commit 9e53abbb7ac71afbccd9f58ec9a964cf84bd16f5 Author: nor..67 Date: Mon Mar 14 12:55:32 2011 +0000 move jme3 to trunk git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@6971 75d07b2b-3a1a-0410-a2c5-0572b91ccdca diff --git a/engine/.classpath b/engine/.classpath new file mode 100644 index 000000000..1fecfb45c --- /dev/null +++ b/engine/.classpath @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/.project b/engine/.project new file mode 100644 index 000000000..8d207ff7c --- /dev/null +++ b/engine/.project @@ -0,0 +1,17 @@ + + + jme3 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/engine/MANIFEST.MF b/engine/MANIFEST.MF new file mode 100644 index 000000000..f6d6f5653 --- /dev/null +++ b/engine/MANIFEST.MF @@ -0,0 +1 @@ +X-Comment: jMonkeyEngine 3.0 \ No newline at end of file diff --git a/engine/TestChooser.exe b/engine/TestChooser.exe new file mode 100644 index 000000000..00f0161d1 Binary files /dev/null and b/engine/TestChooser.exe differ diff --git a/engine/build-android.xml b/engine/build-android.xml new file mode 100644 index 000000000..a5394ed4f --- /dev/null +++ b/engine/build-android.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/build.xml b/engine/build.xml new file mode 100644 index 000000000..5805ecba1 --- /dev/null +++ b/engine/build.xml @@ -0,0 +1,223 @@ + + + + Builds, tests, and runs the project jME3_ordered. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar b/engine/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar new file mode 100644 index 000000000..47dcc3b3a Binary files /dev/null and b/engine/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar differ diff --git a/engine/lib/JWSAntTasks/org-netbeans-modules-javawebstart-anttasks.jar b/engine/lib/JWSAntTasks/org-netbeans-modules-javawebstart-anttasks.jar new file mode 100644 index 000000000..292c9ed28 Binary files /dev/null and b/engine/lib/JWSAntTasks/org-netbeans-modules-javawebstart-anttasks.jar differ diff --git a/engine/lib/android/android.jar b/engine/lib/android/android.jar new file mode 100644 index 000000000..24ae5637d Binary files /dev/null and b/engine/lib/android/android.jar differ diff --git a/engine/lib/antlibs/jsch-0.1.42.jar b/engine/lib/antlibs/jsch-0.1.42.jar new file mode 100644 index 000000000..c65eff095 Binary files /dev/null and b/engine/lib/antlibs/jsch-0.1.42.jar differ diff --git a/engine/lib/jbullet/asm-all-3.1.jar b/engine/lib/jbullet/asm-all-3.1.jar new file mode 100644 index 000000000..5f37af522 Binary files /dev/null and b/engine/lib/jbullet/asm-all-3.1.jar differ diff --git a/engine/lib/jbullet/jbullet.jar b/engine/lib/jbullet/jbullet.jar new file mode 100644 index 000000000..43926d5df Binary files /dev/null and b/engine/lib/jbullet/jbullet.jar differ diff --git a/engine/lib/jbullet/stack-alloc.jar b/engine/lib/jbullet/stack-alloc.jar new file mode 100644 index 000000000..ab1d988ce Binary files /dev/null and b/engine/lib/jbullet/stack-alloc.jar differ diff --git a/engine/lib/jbullet/vecmath.jar b/engine/lib/jbullet/vecmath.jar new file mode 100644 index 000000000..ddfd73bdb Binary files /dev/null and b/engine/lib/jbullet/vecmath.jar differ diff --git a/engine/lib/jheora/LICENSE.jheora b/engine/lib/jheora/LICENSE.jheora new file mode 100644 index 000000000..b40af560b --- /dev/null +++ b/engine/lib/jheora/LICENSE.jheora @@ -0,0 +1,23 @@ +/* Jheora + * Copyright (C) 2004 Fluendo S.L. + * + * Written by: 2004 Wim Taymans + * + * Many thanks to + * The Xiph.Org Foundation http://www.xiph.org/ + * Jheora was based on their Theora reference decoder. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ diff --git a/engine/lib/jheora/cortado-0.6.0.tar.gz b/engine/lib/jheora/cortado-0.6.0.tar.gz new file mode 100644 index 000000000..a8b57300f Binary files /dev/null and b/engine/lib/jheora/cortado-0.6.0.tar.gz differ diff --git a/engine/lib/jheora/jheora-debug-0.6.0.jar b/engine/lib/jheora/jheora-debug-0.6.0.jar new file mode 100644 index 000000000..f7d83e6fc Binary files /dev/null and b/engine/lib/jheora/jheora-debug-0.6.0.jar differ diff --git a/engine/lib/jheora/jheora-jst-debug-0.6.0.jar b/engine/lib/jheora/jheora-jst-debug-0.6.0.jar new file mode 100644 index 000000000..fc1c0a34e Binary files /dev/null and b/engine/lib/jheora/jheora-jst-debug-0.6.0.jar differ diff --git a/engine/lib/jogg/j-ogg-oggd.jar b/engine/lib/jogg/j-ogg-oggd.jar new file mode 100644 index 000000000..1939ee0e7 Binary files /dev/null and b/engine/lib/jogg/j-ogg-oggd.jar differ diff --git a/engine/lib/jogg/j-ogg-vorbisd.jar b/engine/lib/jogg/j-ogg-vorbisd.jar new file mode 100644 index 000000000..77e576931 Binary files /dev/null and b/engine/lib/jogg/j-ogg-vorbisd.jar differ diff --git a/engine/lib/jogl/CHANGELOG.txt b/engine/lib/jogl/CHANGELOG.txt new file mode 100644 index 000000000..1455bec55 --- /dev/null +++ b/engine/lib/jogl/CHANGELOG.txt @@ -0,0 +1,101 @@ +Changes between JOGL 1.1.0 and 1.1.1: + + - Fixed a bug in the checking of incoming buffers' sizes to + glTexImage1D, glTexImage2D, and glTexImage3D. + +Changes between JOGL 1.0.0 and 1.1.0: + + - The glext.h and associated header files JOGL uses have been updated + to OpenGL 2.1 with NVidia's GeForce 8 series extensions. The new + functions are available as methods in the GL interface. + + - The developer build bundles have been changed to zip archives, so + instead of having to download multiple jars, you can now just + download the zip archive for your particular platform. The new zip + archives are versioned with the build date. + + - The source distribution now contains the generated sources like + GL.java, GLU.java, etc. for more convenient use in IDEs. + + - The chosen GLCapabilities are now exposed from the GLDrawable via + GLDrawable.getChosenGLCapabilities(); this functionality works on + all platforms even in cases where the GLCapabilitiesChooser is not + supported, and attempts to provide correct answers so programs can + make decisions based on the results. + + - The native code for the "DRI hack" (to support the open-source DRI + drivers on Linux and other X11 platforms) has been removed; JOGL + now uses the GlueGen NativeLibrary class for this purpose. + Reliability improvements have been made to the implementation of + this class; it has been confirmed as working again with ATI's + proprietary drivers on Linux and should also work better with + NVidia's drivers. + + - The GlueGen runtime classes have been removed from jogl.jar. These + have been factored out into gluegen-rt.jar and are referenced by + both the JOGL and JOAL projects. + + - Thanks to John Burkey some optimizations have been made to the + buffer object-related validity checks in glVertexPointer, etc. as + well as a buffer size query that was being made in the glMapBuffer + implementation. This improves performance for applications + performing a lot of VBO- or vertex array-based rendering, in + particular with the multithreaded OpenGL implementation on Mac OS + X. + + - The JOGL applet launcher now supports deployment of applets which + use both OpenGL for 3D graphics via JOGL as well as OpenAL for + spatialized audio via JOAL. It now prompts the user on Windows + platforms to allow it to enable the -Dsun.java2d.noddraw=true + system property for best robustness. It has been updated for the + changes in the GlueGen runtime classes and native library + structure. Some bugs have been fixed, some of which were preventing + different JOGL-based applets from being deployed from the same + codebase. The documentation and on-line examples have been updated + as well. + + - The TextureIO implementation has been changed to no longer copy the + data associated with BufferedImage TextureData objects. Instead, + the necessary vertical flip is now implemented by flipping the + texture coordinates vertically. + + - An API for updating a sub-image of a Texture object from a + sub-portion of a TextureData object has been added. + + - A GLContext.copy() operation has been added based on community + feedback. + + - Three helper classes have been added to the com.sun.opengl.util.j2d + package to improve interoperability between JOGL and Java 2D: + TextureRenderer, Overlay and TextRenderer. The TextureRenderer + supports drawing into an OpenGL texture using Java 2D. The Overlay + class provides a convenient Java 2D-based overlay on top of an + arbitrary GLDrawable. The TextRenderer class supports drawing of + Java 2D text into an OpenGL context. Thanks to Chris Campbell of + the Java 2D team for collaboration and to the JOGL community for + extensive feedback and testing assistance. + + - Various bug fixes and robustness improvements were made to the + GlueGen runtime, JOGL and GLU implementations. + + - Fixes to the DDSImage class were contributed by the community: a + bug fix to mipmap handling and support for cubemap textures. Thanks + to java.net user bandures. + + - TextureIO.setTexRectEnabled() and isTexRectEnabled() were added + based on feedback from Chris Campbell, in order to simplify the + writing of pixel shaders which have different samplers for + GL_TEXTURE_2D and GL_TEXTURE_RECTANGLE_ARB targets. + + - Thanks to Erik Tollerud, the links to the OpenGL documentation in + the JOGL javadoc were revised to point to the new on-line man pages + in the OpenGL SDK. + + - Support for automatic mipmap generation via GL_GENERATE_MIPMAP was + added to the TextureIO, TextureRenderer and TextRenderer classes. + + - Windows/AMD64 binaries, including the JOGL Cg binding, are now + supplied. + + - Worked around breakage of JOGL with 5.0u10; see Sun bug IDs 6504460 + and 6333613. diff --git a/engine/lib/jogl/COPYRIGHT.txt b/engine/lib/jogl/COPYRIGHT.txt new file mode 100644 index 000000000..360d3748b --- /dev/null +++ b/engine/lib/jogl/COPYRIGHT.txt @@ -0,0 +1,31 @@ + +Copyright 2007 Sun Microsystems, Inc., 4150 Network +Circle, Santa Clara, California 95054, U.S.A. All rights +reserved. + +U.S. Government Rights - Commercial software. Government +users are subject to the Sun Microsystems, Inc. +standard license agreement and applicable provisions of +the FAR and its supplements. + +Use is subject to license terms. + +This distribution may include materials developed by third +parties. + +Sun, Sun Microsystems, the Sun logo and Java are trademarks +or registered trademarks of Sun Microsystems, Inc. in the +U.S. and other countries. + +OpenGL is a registered trademark of Silicon Graphics, Inc. + +This product is covered and controlled by U.S. Export +Control laws and may be subject to the export or import +laws in other countries. Nuclear, missile, chemical +biological weapons or nuclear maritime end uses or end +users, whether direct or indirect, are strictly prohibited. +Export or reexport to countries subject to U.S. embargo or +to entities identified on U.S. export exclusion lists, +including, but not limited to, the denied persons and +specially designated nationals lists is strictly prohibited. + diff --git a/engine/lib/jogl/LICENSE-JOGL-1.1.1.txt b/engine/lib/jogl/LICENSE-JOGL-1.1.1.txt new file mode 100644 index 000000000..cd35e8827 --- /dev/null +++ b/engine/lib/jogl/LICENSE-JOGL-1.1.1.txt @@ -0,0 +1,152 @@ +JOGL is released under the BSD license. The full license terms follow: + + Copyright (c) 2003-2007 Sun Microsystems, Inc. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistribution of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistribution in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of Sun Microsystems, Inc. or the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + This software is provided "AS IS," without a warranty of any kind. ALL + EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + You acknowledge that this software is not designed or intended for use + in the design, construction, operation or maintenance of any nuclear + facility. + +The JOGL source tree contains code ported from the OpenGL sample +implementation by Silicon Graphics, Inc. This code is licensed under +the SGI Free Software License B (Sun is redistributing the modified code +under a slightly modified, alternative license, which is described two +paragraphs below after "NOTE:"): + + License Applicability. Except to the extent portions of this file are + made subject to an alternative license as permitted in the SGI Free + Software License B, Version 1.1 (the "License"), the contents of this + file are subject only to the provisions of the License. You may not use + this file except in compliance with the License. You may obtain a copy + of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 + Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: + + http://oss.sgi.com/projects/FreeB + + Note that, as provided in the License, the Software is distributed on an + "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS + DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND + CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A + PARTICULAR PURPOSE, AND NON-INFRINGEMENT. + + NOTE: The Original Code (as defined below) has been licensed to Sun + Microsystems, Inc. ("Sun") under the SGI Free Software License B + (Version 1.1), shown above ("SGI License"). Pursuant to Section + 3.2(3) of the SGI License, Sun is distributing the Covered Code to + you under an alternative license ("Alternative License"). This + Alternative License includes all of the provisions of the SGI License + except that Section 2.2 and 11 are omitted. Any differences between + the Alternative License and the SGI License are offered solely by Sun + and not by SGI. + + Original Code. The Original Code is: OpenGL Sample Implementation, + Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, + Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. + Copyright in any portions created by third parties is as indicated + elsewhere herein. All Rights Reserved. + + Additional Notice Provisions: The application programming interfaces + established by SGI in conjunction with the Original Code are The + OpenGL(R) Graphics System: A Specification (Version 1.2.1), released + April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version + 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X + Window System(R) (Version 1.3), released October 19, 1998. This software + was created using the OpenGL(R) version 1.2.1 Sample Implementation + published by SGI, but has not been independently verified as being + compliant with the OpenGL(R) version 1.2.1 Specification. + + +The JOGL source tree contains code from the LWJGL project which is +similarly covered by the BSD license: + + Copyright (c) 2002-2004 LWJGL Project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of 'LWJGL' nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The JOGL source tree also contains a Java port of Brian Paul's Tile +Rendering library, used with permission of the author under the BSD +license instead of the original LGPL: + + Copyright (c) 1997-2005 Brian Paul. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistribution of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistribution in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of Brian Paul or the names of contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + This software is provided "AS IS," without a warranty of any + kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + EXCLUDED. THE COPYRIGHT HOLDERS AND CONTRIBUTORS SHALL NOT BE + LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, + MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO + EVENT WILL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY + LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + INABILITY TO USE THIS SOFTWARE, EVEN IF THE COPYRIGHT HOLDERS OR + CONTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/engine/lib/jogl/README.txt b/engine/lib/jogl/README.txt new file mode 100644 index 000000000..98a1ee32b --- /dev/null +++ b/engine/lib/jogl/README.txt @@ -0,0 +1,45 @@ + +Java (TM) Binding for the OpenGL (r) API, version 1.1.1 +------------------------------------------------------- + +This software is licensed by Sun Microsystems, as specified +in the LICENSE-JOGL-1.1.1.txt file. You must use this software +in accordance with the terms under which the code is licensed. + + + +Instructions for unzipping Java Binding for the OpenGL API, version 1.1.1 +-------------------------------------------------------------------- + +After downloading and unzipping the zip file containing the +JOGL release for your target platform, you will see the +following files in the top directory: + + COPYRIGHT.txt + LICENSE-JOGL-1.1.1.txt + Userguide.html + README.txt README file (you are reading it now) + +and the following subdirectory: + + lib contains JOGL implementation + +All of the JOGL implementation files (jar files and native +libraries) are in the lib subdirectory. For instructions on +how to use these implementation files to build or run a JOGL +program see the enclosed JOGL user guide (Userguide.html). + + + +Project source code and getting assistance +------------------------------------------ + +JOGL source code and project information can be found at: + + https://jogl.dev.java.net/ + + +Numerous answers to common questions can be found on the JOGL +forum: + + http://www.javagaming.org/forums/index.php?board=25.0 diff --git a/engine/lib/jogl/Userguide.html b/engine/lib/jogl/Userguide.html new file mode 100644 index 000000000..3dfd57568 --- /dev/null +++ b/engine/lib/jogl/Userguide.html @@ -0,0 +1,999 @@ + + + +Jogl - User's Guide + + + +

Jogl - User's Guide

+ +

+ +

+ +

Overview

+ +

+ +Jogl is a Java programming language binding for the OpenGL 3D graphics +API. It supports integration with the Java platform's AWT and Swing +widget sets while providing a minimal and easy-to-use API that handles +many of the issues associated with building multithreaded OpenGL +applications. Jogl provides access to the latest OpenGL routines +(OpenGL 2.0 with vendor extensions) as well as platform-independent +access to hardware-accelerated offscreen rendering ("pbuffers"). Jogl +also provides some of the most popular features introduced by other +Java bindings for OpenGL like GL4Java, LWJGL and Magician, including a +composable pipeline model which can provide faster debugging for +Java-based OpenGL applications than the analogous C program. + +

+

+ +Jogl was designed for the most recent versions of the Java platform +and for this reason supports only J2SE 1.4 and later. It also only +supports truecolor (15 bits per pixel and higher) rendering; it does +not support color-indexed modes. It was designed with New I/O (NIO) in +mind and uses NIO internally in the implementation. The Jogl binding +is itself written almost completely in the Java programming language. +There are roughly 150 lines of handwritten C code in the entire Jogl +source base (100 of which work around bugs in older OpenGL drivers on +Windows); the rest of the native code is autogenerated during the +build process by a new tool called GlueGen, the source code of +which is available from its own java.net project. + +

+

+ +The JOGL source tree in its current form is an experimental workspace +for the JSR-231 Java +Bindings for OpenGL JSR. JOGL is not the official reference +implementation, but an evolving workspace. Snapshots of the JOGL +source tree are run through the JSR-231 Technology Compatibility Kit +(TCK) to become the official reference implementation (RI). As of this +writing the JSR has not been finalized, so the first official RI of +the JSR has not yet been produced. + +

+ +

Developing with JOGL

+ +

Building the source tree

+ +

+ +Most developers using JOGL will download the most current release +build. Separate instructions are available on how to build +the source tree. + +

+ +

Local installation for development

+ +

+ +The JOGL distribution for developers comes in the form of a zip +archive which contains the Java classes to call OpenGL from Java, as +well as the associated JNI native libraries. JOGL depends on some +run-time support classes and native code provided by the GlueGen +project; these classes and native code are also provided in the zip +bundles. + +

+

+ +If you are developing a new application which uses JOGL, download the +zip archive for your platform (for example., +jogl-[version]-windows-i586.zip) and unzip it. Modify your CLASSPATH +environment variable to include the full paths to jogl.jar and +gluegen-rt.jar; for example, +".;C:\Some\Other\Package\foo.jar;C:\Users\myhome\jogl-[version]-windows-i586\lib\jogl.jar;C:\Users\myhome\jogl-[version]-windows-i586\lib\gluegen-rt.jar". +(If you did not previously set the CLASSPATH environment variable, you +may want to make sure that ".", the current directory, is on your new +CLASSPATH.) Modify your PATH environment variable (Windows), +LD_LIBRARY_PATH environment variable (Solaris and Linux), or +DYLD_LIBRARY_PATH environment variable (Mac OS X) to contain the full +path to the "lib" directory; for example, on Windows, add +"C:\Users\myhome\jogl-[version]-windows-i586\lib" to your PATH using +the System control panel, Advanced tab, Environment Variables +button. At this point your Java installation should be able to see the +JOGL class files. Users of IDEs such as NetBeans and Eclipse should +consult the IDE's documentation to see how to add jar files and native +libraries to their current project. + +

+

+ +Dropping the JOGL jar and native library into the extension directory +of the JRE is strongly discouraged. Doing so can cause +conflicts with third-party applications launched via Java Web Start, +and causes confusion later when upgrading the distribution. + +

+

+ +If you are on the Linux platform, please see the Linux-specific +platform notes, below, with information on incompatibility between the +JPackage Java RPMs and JOGL. + +

+ +

Java Web Start integration

+ +

+ +The recommended distribution vehicle for applications using JOGL is +Java Web Start. JOGL-based applications do not even need to be signed; +all that is necessary is to reference the JOGL extension JNLP file. +Because the JOGL jar files are signed, an unsigned application can +reference the signed JOGL library and continue to run inside the +sandbox. + +

+

+ +To reference JOGL within your application's JNLP file, simply place +the following line in the <resources> section: + +

+  <extension name="jogl" href="http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp" />
+
+ +This JNLP file points to the current JSR-231 unofficial development +build. For reference, the extension JNLP file for the most recent +official JSR-231 build is available at + +
+  <extension name="jogl" href="http://download.java.net/media/jogl/builds/archive/jsr-231-1.1.0/webstart/jogl.jnlp" />
+
+ +Note that before JOGL transitioned to the JSR-231 APIs, there were +releases of the library in the net.java.games.jogl +namespace under version numbers "1.0", "1.1", and "1.1.1". All of +these releases have been superseded by JSR-231. Please update your +applications. + +

+ +

Applet support

+ +

+ +Lilian Chamontin, in conjunction with several other members of the +JOGL community, has contributed a JOGL applet installer. This +installer uses some clever tricks to allow deployment of unsigned +applets which use JOGL into existing web browsers and JREs as far back +as 1.4.2, which is the earliest version of Java supported by JOGL. + +

+

+ +The JOGLAppletInstaller is distributed inside jogl.jar as a utility +class in com.sun.opengl.util. It requires that the developer host a +local, signed copy of jogl.jar and all of the jogl-natives jars; the +certificates must be the same on all of these jars. Note that in the +release builds of JOGL all of these jars are signed by Sun +Microsystems, so the developer can deploy applets without needing any +certificates. + +

+

+ +The JOGLAppletInstaller javadoc describes the basic steps for +deployment of an applet utilizing JOGL. Please refer to this +documentation for more information. A live example of deploying an +unsigned JOGL applet will be added to this documentation shortly once +the first signed build of the JOGLAppletInstaller has been shipped. + +

+ +

GLDrawable and GLContext

+ +

+ +The JSR-231 APIs specify interfaces two low-level OpenGL abstractions: +drawables and contexts. An OpenGL drawable is effectively a surface +upon which OpenGL rendering will be performed. In order to perform +rendering, an OpenGL rendering context is needed. Contexts and +drawables typically go hand-in-hand. More than one context may be +created for a particular drawable. In the JSR-231 abstractions, a +context is always associated with exactly one drawable. + +

+

+ +Most end users will not need to use these abstractions directly. +However, when sharing textures, display lists and other OpenGL objects +between widgets, the concrete identifier for the "namespace" for these +objects is the GLContext. + +

+ +

Creating a GLAutoDrawable

+ +

+ +Jogl provides two basic widgets into which OpenGL rendering can be +performed. The GLCanvas is a heavyweight AWT widget which supports +hardware acceleration and which is intended to be the primary widget +used by applications. The GLJPanel is a fully Swing-compatible +lightweight widget which supports hardware acceleration but which is +not as fast as the GLCanvas because it typically reads back the frame +buffer in order to draw it using Java2D. The GLJPanel is intended to +provide 100% correct Swing integration in the circumstances where a +GLCanvas can not be used. See this +article on The +Swing Connection for more information about mixing lightweight and +heavyweight widgets. See also the section on "Heavyweight and +Lightweight Issues" below. Recent work in the Mustang release of the +JDK has sped up the GLJPanel significantly when the Java2D OpenGL +pipeline is enabled; see this +forum discussion for more details. + +

+

+ +Both the GLCanvas and GLJPanel implement a common interface called +GLAutoDrawable so applications can switch between them with minimal +code changes. The GLAutoDrawable interface provides + +

+ +

+

+ +When creating GLCanvas and GLJPanel instances, the user may request a +certain set of OpenGL parameters in the form of a GLCapabilities +object, customize the format selection algorithm by specifying a +GLCapabilitiesChooser, share textures and display lists with other +GLDrawables, and specify the display device on which the +GLAutoDrawable will be created (GLCanvas only). + +

+

+ +A GLCapabilities object specifies the OpenGL parameters for a +newly-created widget, such as the color, alpha,, z-buffer and +accumulation buffer bit depths and whether the widget is +double-buffered. The default capabilities are loosely specified but +provide for truecolor RGB, a reasonably large depth buffer, +double-buffered, with no alpha, stencil, or accumulation buffers. + +

+

+ +An application can override the default pixel format selection +algorithm by providing a GLCapabilitiesChooser to the GLCanvas or +GLJPanel constructor. (Not all platforms support the +GLCapabilitiesChooser mechanism, however; it may be ignored, in +particular on Mac OS X where pixel format selection is very different +than on other platforms.) The chooseCapabilities method will be called +with all of the available pixel formats as an array of GLCapabilities +objects, as well as the index indicating the window system's +recommended choice; it should return an integer index into this +array. The DefaultGLCapabilitiesChooser uses the window system's +recommendation when it is available, and otherwise attempts to use a +platform-independent selection algorithm. + +

+

+ +The GLJPanel can be made non-opaque according to Swing's rendering +model, so it can act as an overlay to other Swing or Java2D drawing. +In order to enable this, set up your GLCapabilities object with a +non-zero alpha depth (a common value is 8 bits) and call +setOpaque(false) on the GLJPanel once it has been created. Java2D +rendering underneath it will then show through areas where OpenGL has +produced an alpha value less than 1.0. See the JGears and JRefract +demos for examples of how to use this functionality. + +

+ +

Writing a GLEventListener

+ +

+ +Applications implement the GLEventListener interface to perform OpenGL +drawing via callbacks. When the methods of the GLEventListener are +called, the underlying OpenGL context associated with the drawable is +already current. The listener fetches the GL object out of the +GLAutoDrawable and begins to perform rendering. + +

+

+ +The init() method is called when a new OpenGL context is +created for the given GLAutoDrawable. Any display lists or textures +used during the application's normal rendering loop can be safely +initialized in init(). It is important to note that +because the underlying AWT window may be destroyed and recreated while +using the same GLCanvas and GLEventListener, the GLEventListener's +init() method may be called more than once during the +lifetime of the application. The init() method should therefore be +kept as short as possible and only contain the OpenGL initialization +required for the display() method to run properly. It is +the responsibility of the application to keep track of how its various +OpenGL contexts share display lists, textures and other OpenGL objects +so they can be either be reinitialized or so that reinitialization can +be skipped when the init() callback is called. + +

+

+ +Note also that the GLEventListener should be added to the +GLAutoDrawable before the GLAutoDrawable is shown or rendered to for +the first time. If this is not done, it is possible that the init() +method will not be called on the GLEventListener. JOGL does not +maintain internal state to keep track of whether init() has been +called on a particular GLEventListener since the last time an OpenGL +context was created for that GLAutoDrawable. + +

+

+ +The display() method is called to perform per-frame +rendering. The reshape() method is called when the +drawable has been resized; the default implementation automatically +resizes the OpenGL viewport so often it is not necessary to do any +work in this method. The displayChanged() method is +designed to allow applications to support on-the-fly screen mode +switching, but support for this is not yet implemented so the body of +this method should remain empty. + +

+

+ +It is strongly recommended that applications always refetch the GL +object out of the GLAutoDrawable upon each call to the +init(), display() and reshape() +methods and pass the GL object down on the stack to any drawing +routines, as opposed to storing the GL in a field and referencing it +from there. The reason is that multithreading issues inherent to the +AWT toolkit make it difficult to reason about which threads certain +operations are occurring on, and if the GL object is stored in a field +it is unfortunately too easy to accidentally make OpenGL calls from a +thread that does not have a current context. This will usually cause +the application to crash. For more information please see the section +on multithreading. + +

+ +

Using the Composable Pipeline

+ +

+ +Jogl supports the "composable pipeline" paradigm introduced by the +Magician Java binding for OpenGL. The DebugGL pipeline calls +glGetError after each OpenGL call, reporting any errors +found. It can greatly speed up development time because of its +fine-grained error checking as opposed to the manual error checking +usually required in OpenGL programs written in C. The TraceGL prints +logging information upon each OpenGL call and is helpful when an +application crash makes it difficult to see where the error occurred. + +

+

+ +To use these pipelines, call GLAutoDrawable.setGL at the +beginning of the init method in your GLEventListener. For +example, + +

+class MyListener implements GLEventListener {
+  public void init(GLDrawable drawable) {
+    drawable.setGL(new DebugGL(drawable.getGL()));
+    // ...
+  }
+
+  // ...
+}
+
+ +

+

+ +Note that the GLAutoDrawable.setGL() method simply calls setGL() on +the default OpenGL context created by the GLAutoDrawable, so +sophisticated applications creating their own OpenGL contexts can use +the composable pipeline with these contexts by setting the GL object +in the context object itself. The composable pipeline needs to be +re-installed every time GLContext.makeCurrent() returns +CONTEXT_CURRENT_NEW. + +

+ +

Heavyweight and Lightweight Issues

+ +

+ +As mentioned above, JOGL supplies both a heavyweight (GLCanvas) and a +lightweight (GLJPanel) widget to be able to provide the fastest +possible performance for applications which need it as well as 100% +correct Swing integration, again for applications which need it. The +GLCanvas usually provides higher performance than the GLJPanel, though +in recent releases the GLJPanel's speed has been improved when the +Java2D/OpenGL pipeline is active as described in this +forum discussion. Nonetheless, the GLCanvas can be used in almost +every kind of application except those using JInternalFrames. Please +see the Swing Connection article mentioned above for details on mixing +heavyweight and lightweight widgets. A couple of common pitfalls are +described here. + +

+

+ +When using JPopupMenus or Swing tool tips in conjunction with the +GLCanvas, it is necessary to disable the use of lightweight widgets +for the popups. See the methods +ToolTipManager.setLightWeightPopupEnabled, +JPopupMenu.setLightWeightPopupEnabled, and +JPopupMenu.setDefaultLightWeightPopupEnabled. + +

+

+ +There are occasionally problems with certain LayoutManagers and +component configurations where if a GLCanvas is placed in the middle +of a set of lightweight widgets then it may only grow and never +shrink. These issues are documented somewhat in JOGL Issue +135 and most recently in the thread "Resize +behaviour" in the JOGL forum. The root cause is behavior of the +Canvas, and in particular its ComponentPeer. The implementation of +getPreferredSize() calls getMinimumSize() and getMinimumSize() turns +around and calls Component.getSize(). This effectively means that the +Canvas will report its preferred size as being as large as the +component has ever been. For some layout managers this doesn't seem to +matter, but for others like the BoxLayout it does. See the test case +attached to Issue 135 for an example. Replacing the GLCanvas with an +ordinary Canvas yields the same behavior. + +

+

+ +One suggestion was to override getPreferredSize() so that if a +preferred size has not been set by the user, to default to (0, +0). This works fine for some test cases but breaks all of the other +JOGL demos because they use a different LayoutManager. There appear to +be a lot of interactions between heavyweight vs. lightweight widgets +and layout managers. One experiment which was done was to override +setSize() in GLCanvas to update the preferred size. This works down +to the size specified by the user; if the window is resized any +smeller the same problem appears. If reshape() (the base routine of +setSize(), setBounds(), etc.) is changed to do the same thing, the +demo breaks in the same way it originally did. Therefore this solution +is fragile because it isn't clear which of these methods are used +internally by the AWT and for what purposes. + +

+

+ +There are two possible solutions, both application-specific. The best +and most portable appears to be to put the GLCanvas into a JPanel and +set the JPanel's preferred size to (0, 0). The JPanel will cause this +constraint to be enforced on its contained GLCanvas. The other +workaround is to call setPreferredSize(new Dimension(0, +0)) on a newly-created GLCanvas; this method is new in 1.5. + +

+

+ +Another issue that occasionally arises on Windows is flickering during +live resizing of a GLCanvas. This is caused by the AWT's repainting +the background of the Canvas and can not be overridden on a per-Canvas +basis, for example when subclassing Canvas into GLCanvas. The +repainting of the background of Canvases on Windows can be disabled by +specifying the system property +-Dsun.awt.noerasebackground=true. Whether to specify this +flag depends on the application and should not be done universally, +but instead on a case-by-case basis. Some more detail is in the thread +"TIP: +JOGL + Swing flicker" in the JOGL forum. + +

+ +

Multithreading Issues

+ +

+ +Jogl was designed to interoperate with the AWT, an inherently +multithreaded GUI toolkit. OpenGL, in contrast, was originally +designed in single-threaded C programming environments. For this +reason Jogl provides a framework in which it is possible to write +correct multithreaded OpenGL applications using the GLEventListener +paradigm. + +

+

+ +If an application written using Jogl interacts in any way with the +mouse or keyboard, the AWT is processing these events and the +multithreaded aspects of the program must be considered. + +

+

+ +OpenGL applications usually behave in one of two ways: either they +repaint only on demand, for example when mouse input comes in, or they +repaint continually, regardless of whether user input is coming in. In +the repaint-on-demand model, the application can merely call +GLAutoDrawable.display() manually at the end of the mouse +or keyboard listener to cause repainting to be done. Alternatively if +the application knows the concrete type of the GLDrawable it can call +repaint() to have the painting scheduled for a later time. + +

+

+ +In the continuous repaint model, the application typically has a main +loop which is calling GLAutoDrawable.display() +repeatedly, or is using the Animator class, which does this +internally. In both of these cases the OpenGL rendering will be done +on this thread rather than the internal AWT event queue thread which +dispatches mouse and keyboard events. + +

+

+ +Both of these models (repaint-on-demand and repaint continually) still +require the user to think about which thread keyboard and mouse events +are coming in on, and which thread is performing the OpenGL rendering. +OpenGL rendering may not occur directly inside the mouse or +keyboard handlers, because the OpenGL context for the drawable is not +current at this point (hence the warning about storing a GL object in +a field, where it can be fetched and accidentally used by another +thread). However, a mouse or keyboard listener may invoke +GLAutoDrawable.display(). + +

+

+ +It is generally recommended that applications perform as little work +as possible inside their mouse and keyboard handlers to keep the GUI +responsive. However, since OpenGL commands can not be run from +directly within the mouse or keyboard event listener, the best +practice is to store off state when the listener is entered and +retrieve this state during the next call to +GLEventListener.display(). + +

+

+ +Furthermore, it is recommended that if there are long computational +sequences in the GLEventListener's display method which +reference variables which may be being simultaneously modified by the +AWT thread (mouse and keyboard listeners) that copies of these +variables be made upon entry to display and these copies +be referenced throughout display() and the methods it calls. This will +prevent the values from changing while the OpenGL rendering is being +performed. Errors of this kind show up in many ways, including certain +kinds of flickering of the rendered image as certain pieces of objects +are rendered in one place and other pieces are rendered elsewhere in +the scene. Restructuring the display() method as described has solved +all instances of this kind of error that have been seen with Jogl to +date. + +

+

+ +Prior to Jogl 1.1 b10, the Jogl library attempted to give applications +strict control over which thread or threads performed OpenGL +rendering. The setRenderingThread(), +setNoAutoRedrawMode() and display() APIs +were originally designed to allow the application to create its own +animation thread and avoid OpenGL context switching on platforms that +supported it. Unfortunately, serious stability issues caused by +multithreading bugs in either vendors' OpenGL drivers or in the Java +platform implementation have arisen on three of Jogl's major supported +platforms: Windows, Linux and Mac OS X. In order to address these +bugs, the threading model in Jogl 1.1 b10 and later has changed. + +

+

+ +All GLEventListener callbacks and other internal OpenGL context +management are now performed on one thread. (In the current +implementation, this thread is the AWT event queue thread, which is a +thread internal to the implementation of the AWT and which is always +present when the AWT is being used. Future versions of Jogl may change +the thread on which the OpenGL work is performed.) When the +GLAutoDrawable.display() method is called from user code, +it now performs the work synchronously on the AWT event queue thread, +even if the calling thread is a different thread. The +setRenderingThread() optimization is now a no-op. The +setNoAutoRedrawMode() API still works as previously +advertised, though now that all work is done on the AWT event queue +thread it no longer needs to be used in most cases. (It was previously +useful for working around certain kinds of OpenGL driver bugs.) + +

+

+ +Most applications will not see a change in behavior from this change +in the Jogl implementation. Applications which use thread-local +storage or complex multithreading and synchronization may see a change +in their control flow requiring code changes. While it is strongly +recommended to change such applications to work under the new +threading model, the old threading model can be used by specifying the +system property -Djogl.1thread=auto or +-Djogl.1thread=false. The "auto" setting is equivalent to +the behavior in 1.1 b09 and before, where on ATI cards the +single-threaded mode would be used. The "false' setting is equivalent +to disabling the single-threaded mode. "true" is now the default +setting. + +

+

+ +In the JSR-231 APIs the single-threaded behavior continues to be the +default and the setRenderingThread() and +setNoAutoRedrawMode() APIs have been removed. The public +Threading class still provides some control over the +internal use of threads in the library as well as external access to +these mechanisms. + +

+ +

Pbuffers

+ +

+ +Jogl exposes hardware-accelerated offscreen rendering (pbuffers) with +a minimal and platform-agnostic API. Several recent demos have been +successfully ported from C/C++ to Java using Jogl's pbuffer APIs. +However, the pbuffer support in Jogl remains one of the more +experimental aspects of the package and the APIs may need to change in +the future. + +

+

+ +To create a pbuffer, call +GLDrawableFactory.createGLPbuffer(). It is wise to call +GLDrawableFactory.canCreateGLPbuffer() first to ensure +the graphics card has pbuffer support first. The pbuffer is created +immediately and is available for rendering as soon as +createGLPbuffer returns. + +

+

+ +A pbuffer is used in conjunction with the GLEventListener mechanism by +calling its display() method. Rendering, as always, occurs while the +pbuffer's OpenGL context is current. There are render-to-texture +options that can be specified in the GLCapabilities for the pbuffer +which can make it easier to operate upon the resulting pixels. These +APIs are however highly experimental and not yet implemented on all +platforms. + +

+ +

GLU

+ +

+ +Jogl contains support for the GLU (OpenGL Utility Library) version +1.3. Jogl originally supported GLU by wrapping the C version of the +APIs, but over time, and thanks to the contributions of several +individuals, it now uses a pure-Java version of SGI's GLU library. The +pure Java port is enabled by default, and addresses stability issues +on certain Linux distributions as well as the lack of native GLU 1.3 +support on the Windows platform. In case of problems with the Java +port, the C version of the GLU library may be used by specifying the +system property -Djogl.glu.nojava on the command +line. All of the same functionality is exposed with both the Java and +C versions of the GLU library; currently NURBS support is the only +missing feature on both sides. If you run into problems with the Java +port of the GLU library please file a bug using the Issue Tracker on +the Jogl home page. + +

+

+ +To use the GLU, simply instantiate a GLU object via new +GLU() at the beginning of your program. The methods on the GLU +object may be called at any point when an OpenGL context is current. +Because the GLU implementation is not thread-safe, one GLU object +should be created for each GLEventListener or other entity performing +OpenGL rendering in a given thread. + +

+ +

More Resources

+ +

+ +The JOGL +forum on javagaming.org is +the best place to ask questions about the library. Many users, as well +as the Jogl developers, read this forum frequently, and the archived +threads contain a lot of useful information (which still needs to be +distilled into documentation). + +

+

+ +The JOGL demos provide +several examples of usage of the library. + +

+

+ +Pepijn Van Eeckhoudt, Kevin Duling and Abdul Bezrati have done JOGL ports of +many of the the NeHe demos. These are small examples of various +pieces of OpenGL functionality. See also the NeHe web site. + +

+

+ +Pepijn also did a JOGL port of +Paolo Martella's GLExcess +demo. To see the news update about this port, go to the main GLExcess +site and scroll down. + +

+

+ +Gregory Pierce's introduction +to JOGL is a useful tutorial on starting to use the JOGL library. + +

+

+ +For release information about the JOGL library, please see the JOGL Release +Information thread on the JOGL forum on javagaming.org. + +

+

+ +Please post on the JOGL forum if you have a resource you'd like to add +to this documentation. + +

+ +

Platform Notes

+ +

All Platforms

+ +

+ +The following issues, among others, are outstanding on all platforms: + +

+ + + +

Windows

+ +

+ +For correct operation, it is necessary to specify the system property +-Dsun.java2d.noddraw=true when running JOGL applications +on Windows; this system property disables the use of DirectDraw by +Java2D. There are driver-level incompatibilities between DirectDraw +and OpenGL which manifest themselves as application crashes, poor +performance, bad flickering, and other artifacts. This poor behavior +may exhibit itself when OpenGL and DirectDraw are simply used in the +same application, not even just in the same window, so disabling +Java2D's DirectDraw pipeline and forcing it to use its GDI pipeline is +the only way to work around these issues. Java Web Start applications +may set this system property by adding the following line to the +<resources> section of the JNLP file:

+<property name="sun.java2d.noddraw" value="true"/> 
+ +

+

+ +There is a serious memory leak in ATI's OpenGL drivers which is +exhibited on Windows XP on Mobility Radeon 9700 hardware. It's +possible it will be present on other hardware as well though it was +not reproducible at the time of this writing on desktop Radeon +hardware or older ATI mobile chips. The bug is documented in JOGL Issue +166 and a bug has been filed with ATI. You can confirm the +presence of the bug either with the test case in that bug report or by +simply running the Gears demo; if the process size grows over time in +the Task Manager, the memory leak is present on your hardware. For the +time being, you can work around this memory leak by specifying the +system property -Djogl.GLContext.nofree on the command +line when launching your JOGL applications. There is no good +general-purpose workaround for this bug which behaves well on all +hardware. + +

+ +

Linux

+ +

+ +The Sun JDK "compatibility" RPMs (java-1.5.0-sun-compat, +java-1.6.0-sun-compat) provided by jpackage.org are incompatible with +JOGL. These RPMs symlink an internal JDK directory to /usr/lib, which +overrides how both NVidia and ATI currently provide their drivers to +some Linux distributions, which is through an override in +/etc/ld.so.conf (actually, in /etc/ld.so.conf.d). The implicit +presence of /usr/lib on LD_LIBRARY_PATH forces the /usr/lib/libGL.so.1 +version of OpenGL to be used, which is typically Mesa and which will +provide only software rendering. + +

+

+ +Unfortunately the JPackage maintainers have so far been unreceptive to +changing their installation mechanism; see this +mailing list posting. Until this is resolved, we strongly +discourage the use of the JPackage installers for the Sun JDK. +Instead, download the JRE or JDK installers directly from Sun's +website. + +

+

+ +Archived forum postings illustrating this problem are here +and here. + +

+ +

Solaris, Linux (X11 platforms)

+ +

+ +Support has been added to the JOGL library for allowing multiple +threads to each have an OpenGL context current simultaneously, for +example to implement multi-head CAVE-like environments. Normally a +global AWT lock is held between calls to GLContext.makeCurrent() / +release() for on-screen heavyweight contexts (for example, those +associated with a Canvas or GLCanvas). We have found this to be +necessary for stability purposes on all supported X11 platforms, even +with relatively robust drivers such as those from NVidia. + +

+

+ +To enable multiple GLContexts to be made current simultaneously on X11 +platforms, specify the command line argument +-Djogl.GLContext.optimize when starting the JVM. Note +that this may incur robustness problems, in particular when resizing +or moving windows. We have also found that ATI's proprietary drivers +do not work at all with this flag, apparently because they cause GLX +tokens to be sent to the X server for various GL calls even for direct +contexts. For this reason if the GLX vendor is ATI then this flag +currently has no effect. + +

+ +

Mac OS X

+ +

+ +There are some problems with visual artifacts and stability problems +with some of the Jogl demos on Mac OS X. It appears that at least some +of these problems are due to bugs in Apple's OpenGL support. Bugs have +been filed about these problems and it is hoped they will be addressed +in the near future. + +

+

+ +The Mac OS X port of Jogl, in particular the GL interface and its +implementation, can be used either with the provided GLCanvas widget +or with the Cocoa NSOpenGLView. In order to use it with Cocoa the +following steps should be taken: + +

+ +NOTE: the Cocoa interoperability has not been retested +recently, though similar interoperability has been tested on other +platforms. Please report any problems found with using Jogl with an +NSOpenGLView. + +

+

+ +The following issues remain with the Mac OS X port: + +

+ +

+ +

Version History

+ +

+ +JOGL's version history can be found online in the "JOGL Release +Information" thread in the JOGL forum. Comments about the 1.1 +release train are in the thread "JOGL 1.1 +released". + + + + diff --git a/engine/lib/jogl/gluegen-rt.jar b/engine/lib/jogl/gluegen-rt.jar new file mode 100644 index 000000000..7ac10a354 Binary files /dev/null and b/engine/lib/jogl/gluegen-rt.jar differ diff --git a/engine/lib/jogl/jME3-jogl-natives.jar b/engine/lib/jogl/jME3-jogl-natives.jar new file mode 100644 index 000000000..5b8c877d9 Binary files /dev/null and b/engine/lib/jogl/jME3-jogl-natives.jar differ diff --git a/engine/lib/jogl/jogl.jar b/engine/lib/jogl/jogl.jar new file mode 100644 index 000000000..305e99873 Binary files /dev/null and b/engine/lib/jogl/jogl.jar differ diff --git a/engine/lib/jogl/macosx_ppc/libgluegen-rt.jnilib b/engine/lib/jogl/macosx_ppc/libgluegen-rt.jnilib new file mode 100644 index 000000000..0ed9ba1b2 Binary files /dev/null and b/engine/lib/jogl/macosx_ppc/libgluegen-rt.jnilib differ diff --git a/engine/lib/jogl/macosx_ppc/libjogl.jnilib b/engine/lib/jogl/macosx_ppc/libjogl.jnilib new file mode 100644 index 000000000..66ba6d12c Binary files /dev/null and b/engine/lib/jogl/macosx_ppc/libjogl.jnilib differ diff --git a/engine/lib/jogl/macosx_ppc/libjogl_awt.jnilib b/engine/lib/jogl/macosx_ppc/libjogl_awt.jnilib new file mode 100644 index 000000000..55d38655e Binary files /dev/null and b/engine/lib/jogl/macosx_ppc/libjogl_awt.jnilib differ diff --git a/engine/lib/jogl/macosx_ppc/libjogl_cg.jnilib b/engine/lib/jogl/macosx_ppc/libjogl_cg.jnilib new file mode 100644 index 000000000..d9d06dced Binary files /dev/null and b/engine/lib/jogl/macosx_ppc/libjogl_cg.jnilib differ diff --git a/engine/lib/jogl/macosx_universal/libgluegen-rt.jnilib b/engine/lib/jogl/macosx_universal/libgluegen-rt.jnilib new file mode 100644 index 000000000..195628012 Binary files /dev/null and b/engine/lib/jogl/macosx_universal/libgluegen-rt.jnilib differ diff --git a/engine/lib/jogl/macosx_universal/libjogl.jnilib b/engine/lib/jogl/macosx_universal/libjogl.jnilib new file mode 100644 index 000000000..eb8719aa9 Binary files /dev/null and b/engine/lib/jogl/macosx_universal/libjogl.jnilib differ diff --git a/engine/lib/jogl/macosx_universal/libjogl_awt.jnilib b/engine/lib/jogl/macosx_universal/libjogl_awt.jnilib new file mode 100644 index 000000000..2f16fbfb6 Binary files /dev/null and b/engine/lib/jogl/macosx_universal/libjogl_awt.jnilib differ diff --git a/engine/lib/jogl/macosx_universal/libjogl_cg.jnilib b/engine/lib/jogl/macosx_universal/libjogl_cg.jnilib new file mode 100644 index 000000000..562712108 Binary files /dev/null and b/engine/lib/jogl/macosx_universal/libjogl_cg.jnilib differ diff --git a/engine/lib/jogl/win32/gluegen-rt.dll b/engine/lib/jogl/win32/gluegen-rt.dll new file mode 100644 index 000000000..281d389d1 Binary files /dev/null and b/engine/lib/jogl/win32/gluegen-rt.dll differ diff --git a/engine/lib/jogl/win32/jogl.dll b/engine/lib/jogl/win32/jogl.dll new file mode 100644 index 000000000..ee7a6a590 Binary files /dev/null and b/engine/lib/jogl/win32/jogl.dll differ diff --git a/engine/lib/jogl/win32/jogl_awt.dll b/engine/lib/jogl/win32/jogl_awt.dll new file mode 100644 index 000000000..2b47eee6e Binary files /dev/null and b/engine/lib/jogl/win32/jogl_awt.dll differ diff --git a/engine/lib/jogl/win32/jogl_cg.dll b/engine/lib/jogl/win32/jogl_cg.dll new file mode 100644 index 000000000..c64e2a2ea Binary files /dev/null and b/engine/lib/jogl/win32/jogl_cg.dll differ diff --git a/engine/lib/jogl/win64/gluegen-rt.dll b/engine/lib/jogl/win64/gluegen-rt.dll new file mode 100644 index 000000000..a7eb6f5b9 Binary files /dev/null and b/engine/lib/jogl/win64/gluegen-rt.dll differ diff --git a/engine/lib/jogl/win64/jogl.dll b/engine/lib/jogl/win64/jogl.dll new file mode 100644 index 000000000..990986f19 Binary files /dev/null and b/engine/lib/jogl/win64/jogl.dll differ diff --git a/engine/lib/jogl/win64/jogl_awt.dll b/engine/lib/jogl/win64/jogl_awt.dll new file mode 100644 index 000000000..d247b4a0b Binary files /dev/null and b/engine/lib/jogl/win64/jogl_awt.dll differ diff --git a/engine/lib/jogl/win64/jogl_cg.dll b/engine/lib/jogl/win64/jogl_cg.dll new file mode 100644 index 000000000..9e2f371b0 Binary files /dev/null and b/engine/lib/jogl/win64/jogl_cg.dll differ diff --git a/engine/lib/jogl2/gluegen-rt.jar b/engine/lib/jogl2/gluegen-rt.jar new file mode 100644 index 000000000..60e871f72 Binary files /dev/null and b/engine/lib/jogl2/gluegen-rt.jar differ diff --git a/engine/lib/jogl2/jogl.all.jar b/engine/lib/jogl2/jogl.all.jar new file mode 100644 index 000000000..d8b6da4ee Binary files /dev/null and b/engine/lib/jogl2/jogl.all.jar differ diff --git a/engine/lib/jogl2/linux32/libgluegen-rt.so b/engine/lib/jogl2/linux32/libgluegen-rt.so new file mode 100644 index 000000000..e6730cec5 Binary files /dev/null and b/engine/lib/jogl2/linux32/libgluegen-rt.so differ diff --git a/engine/lib/jogl2/linux32/libjogl_desktop.so b/engine/lib/jogl2/linux32/libjogl_desktop.so new file mode 100644 index 000000000..a88ae3f1d Binary files /dev/null and b/engine/lib/jogl2/linux32/libjogl_desktop.so differ diff --git a/engine/lib/jogl2/linux32/libnativewindow_awt.so b/engine/lib/jogl2/linux32/libnativewindow_awt.so new file mode 100644 index 000000000..77a189a8f Binary files /dev/null and b/engine/lib/jogl2/linux32/libnativewindow_awt.so differ diff --git a/engine/lib/jogl2/linux32/libnativewindow_x11.so b/engine/lib/jogl2/linux32/libnativewindow_x11.so new file mode 100644 index 000000000..1b9d885fd Binary files /dev/null and b/engine/lib/jogl2/linux32/libnativewindow_x11.so differ diff --git a/engine/lib/jogl2/linux32/libnewt.so b/engine/lib/jogl2/linux32/libnewt.so new file mode 100644 index 000000000..6159024a3 Binary files /dev/null and b/engine/lib/jogl2/linux32/libnewt.so differ diff --git a/engine/lib/jogl2/nativewindow.all.jar b/engine/lib/jogl2/nativewindow.all.jar new file mode 100644 index 000000000..8e9fa19e9 Binary files /dev/null and b/engine/lib/jogl2/nativewindow.all.jar differ diff --git a/engine/lib/jogl2/nativewindow.os.x11.jar b/engine/lib/jogl2/nativewindow.os.x11.jar new file mode 100644 index 000000000..6a8303be6 Binary files /dev/null and b/engine/lib/jogl2/nativewindow.os.x11.jar differ diff --git a/engine/lib/jogl2/newt.all.jar b/engine/lib/jogl2/newt.all.jar new file mode 100644 index 000000000..de21435d7 Binary files /dev/null and b/engine/lib/jogl2/newt.all.jar differ diff --git a/engine/lib/jogl2/newt.os.x11.jar b/engine/lib/jogl2/newt.os.x11.jar new file mode 100644 index 000000000..da8d04be7 Binary files /dev/null and b/engine/lib/jogl2/newt.os.x11.jar differ diff --git a/engine/lib/junit_4/junit-4.5-api.zip b/engine/lib/junit_4/junit-4.5-api.zip new file mode 100644 index 000000000..5748c444d Binary files /dev/null and b/engine/lib/junit_4/junit-4.5-api.zip differ diff --git a/engine/lib/junit_4/junit-4.5-src.jar b/engine/lib/junit_4/junit-4.5-src.jar new file mode 100644 index 000000000..18774a573 Binary files /dev/null and b/engine/lib/junit_4/junit-4.5-src.jar differ diff --git a/engine/lib/junit_4/junit-4.5.jar b/engine/lib/junit_4/junit-4.5.jar new file mode 100644 index 000000000..83f8bc793 Binary files /dev/null and b/engine/lib/junit_4/junit-4.5.jar differ diff --git a/engine/lib/lwjgl/3rdparty/jinput_license.txt b/engine/lib/lwjgl/3rdparty/jinput_license.txt new file mode 100644 index 000000000..cee4669ba --- /dev/null +++ b/engine/lib/lwjgl/3rdparty/jinput_license.txt @@ -0,0 +1,32 @@ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ \ No newline at end of file diff --git a/engine/lib/lwjgl/3rdparty/jogl_license.txt b/engine/lib/lwjgl/3rdparty/jogl_license.txt new file mode 100644 index 000000000..db9b93374 --- /dev/null +++ b/engine/lib/lwjgl/3rdparty/jogl_license.txt @@ -0,0 +1,152 @@ +JOGL is released under the BSD license. The full license terms follow: + + Copyright (c) 2003-2009 Sun Microsystems, Inc. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistribution of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistribution in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of Sun Microsystems, Inc. or the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + This software is provided "AS IS," without a warranty of any kind. ALL + EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + You acknowledge that this software is not designed or intended for use + in the design, construction, operation or maintenance of any nuclear + facility. + +The JOGL source tree contains code ported from the OpenGL sample +implementation by Silicon Graphics, Inc. This code is licensed under +the SGI Free Software License B (Sun is redistributing the modified code +under a slightly modified, alternative license, which is described two +paragraphs below after "NOTE:"): + + License Applicability. Except to the extent portions of this file are + made subject to an alternative license as permitted in the SGI Free + Software License B, Version 1.1 (the "License"), the contents of this + file are subject only to the provisions of the License. You may not use + this file except in compliance with the License. You may obtain a copy + of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 + Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: + + http://oss.sgi.com/projects/FreeB + + Note that, as provided in the License, the Software is distributed on an + "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS + DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND + CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A + PARTICULAR PURPOSE, AND NON-INFRINGEMENT. + + NOTE: The Original Code (as defined below) has been licensed to Sun + Microsystems, Inc. ("Sun") under the SGI Free Software License B + (Version 1.1), shown above ("SGI License"). Pursuant to Section + 3.2(3) of the SGI License, Sun is distributing the Covered Code to + you under an alternative license ("Alternative License"). This + Alternative License includes all of the provisions of the SGI License + except that Section 2.2 and 11 are omitted. Any differences between + the Alternative License and the SGI License are offered solely by Sun + and not by SGI. + + Original Code. The Original Code is: OpenGL Sample Implementation, + Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, + Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. + Copyright in any portions created by third parties is as indicated + elsewhere herein. All Rights Reserved. + + Additional Notice Provisions: The application programming interfaces + established by SGI in conjunction with the Original Code are The + OpenGL(R) Graphics System: A Specification (Version 1.2.1), released + April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version + 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X + Window System(R) (Version 1.3), released October 19, 1998. This software + was created using the OpenGL(R) version 1.2.1 Sample Implementation + published by SGI, but has not been independently verified as being + compliant with the OpenGL(R) version 1.2.1 Specification. + + +The JOGL source tree contains code from the LWJGL project which is +similarly covered by the BSD license: + + Copyright (c) 2002-2004 LWJGL Project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of 'LWJGL' nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The JOGL source tree also contains a Java port of Brian Paul's Tile +Rendering library, used with permission of the author under the BSD +license instead of the original LGPL: + + Copyright (c) 1997-2005 Brian Paul. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistribution of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistribution in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of Brian Paul or the names of contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + This software is provided "AS IS," without a warranty of any + kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + EXCLUDED. THE COPYRIGHT HOLDERS AND CONTRIBUTORS SHALL NOT BE + LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, + MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO + EVENT WILL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY + LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + INABILITY TO USE THIS SOFTWARE, EVEN IF THE COPYRIGHT HOLDERS OR + CONTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/engine/lib/lwjgl/3rdparty/lzma_license.txt b/engine/lib/lwjgl/3rdparty/lzma_license.txt new file mode 100644 index 000000000..d82521961 --- /dev/null +++ b/engine/lib/lwjgl/3rdparty/lzma_license.txt @@ -0,0 +1,15 @@ +LZMA# SDK is licensed under two licenses: + +1) GNU Lesser General Public License (GNU LGPL) +2) Common Public License (CPL) + +It means that you can select one of these two licenses and +follow rules of that license. + +SPECIAL EXCEPTION +Igor Pavlov, as the author of this code, expressly permits you +to statically or dynamically link your code (or bind by name) +to the files from LZMA# SDK without subjecting your linked +code to the terms of the CPL or GNU LGPL. +Any modifications or additions to files from LZMA# SDK, however, +are subject to the GNU LGPL or CPL terms. \ No newline at end of file diff --git a/engine/lib/lwjgl/3rdparty/openal_license.txt b/engine/lib/lwjgl/3rdparty/openal_license.txt new file mode 100644 index 000000000..339560d0d --- /dev/null +++ b/engine/lib/lwjgl/3rdparty/openal_license.txt @@ -0,0 +1,437 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/engine/lib/lwjgl/CREDITS b/engine/lib/lwjgl/CREDITS new file mode 100644 index 000000000..8f1a038fb --- /dev/null +++ b/engine/lib/lwjgl/CREDITS @@ -0,0 +1,36 @@ +The following people have helped to make this project what it is today: + - Caspian Rychlik-Prince + - Brian Matzon + - Elias Naur + - Ioannis Tsakpinis + - Niels Jørgensen + - Tristan Campbell + - Gregory Pierce + - Luke Holden + - Mark Bernard + - Erik Duijs + - Jos Hirth + - Kevin Glass + - Atsuya Takagi + - kappaOne + - Simon Felix + - Ryan McNally + - Ciardhubh + - Jens von Pilgrim + - Ruben Garat + +additional credits goes to: + - Joseph I. Valenzuela [OpenAL stuff] + - Lev Povalahev [OpenGL Extensions] + - Endolf [Nightly builds and JInput] + +The LWJGL project includes files from or depends on the following projects: + - OpenGL, SGI - http://opengl.org/ + - OpenAL, Creative Labs - http://openal.org/ + - jinput, Sun - https://jinput.dev.java.net/ + - lzma, p7zip - http://p7zip.sourceforge.net/ + - JOGL, Sun - http://kenai.com/projects/jogl/pages/Home + +Please see the /doc/3rdparty/ directory for licenses. + +All trademarks and registered trademarks are the property of their respective owners. diff --git a/engine/lib/lwjgl/LICENSE b/engine/lib/lwjgl/LICENSE new file mode 100644 index 000000000..d277220e1 --- /dev/null +++ b/engine/lib/lwjgl/LICENSE @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2008 Lightweight Java Game Library Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'Light Weight Java Game Library' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ \ No newline at end of file diff --git a/engine/lib/lwjgl/README b/engine/lib/lwjgl/README new file mode 100644 index 000000000..977ae0214 --- /dev/null +++ b/engine/lib/lwjgl/README @@ -0,0 +1,50 @@ +This is the official readme file for lwjgl. + +Unless otherwise stated, all files distributed or in SVN are covered by +the license as stated in the LICENSE file. If you have not received this +file, please download it from the cvs server. + +To run some of the included tests: + Extract the archive, and cd into directory + (please substitute ; and \ according to platform) + + java -cp .;res;jar\lwjgl.jar;jar\lwjgl_test.jar;jar\lwjgl_util.jar;jar\jinput.jar; -Djava.library.path=native\ TEST + (this specifies that the jvm should locate the lwjgl native libs in 'native' directory) + + where TEST is some of the following: + + org.lwjgl.test.WindowCreationTest + org.lwjgl.test.SysTest + org.lwjgl.test.DisplayTest + + org.lwjgl.test.input.MouseCreationTest + org.lwjgl.test.input.MouseTest + org.lwjgl.test.input.HWCursorTest + org.lwjgl.test.input.KeyboardTest + org.lwjgl.test.input.TestControllers + + org.lwjgl.test.openal.ALCTest + org.lwjgl.test.openal.OpenALCreationTest + org.lwjgl.test.openal.MovingSoundTest + org.lwjgl.test.openal.PlayTest + org.lwjgl.test.openal.PlayTestMemory + org.lwjgl.test.openal.SourceLimitTest + org.lwjgl.test.openal.PositionTest + org.lwjgl.test.openal.StressTest + org.lwjgl.test.openal.SourceLimitTest + + org.lwjgl.test.opengl.FullScreenWindowedTest + org.lwjgl.test.opengl.PbufferTest + org.lwjgl.test.opengl.VBOIndexTest + org.lwjgl.test.opengl.VBOTest + + org.lwjgl.test.opengl.pbuffers.PbufferTest + + org.lwjgl.test.opengl.shaders.ShadersTest + +You may also run the Space invaders demo by executing: + java -cp .;res;jar\lwjgl.jar;jar\lwjgl_test.jar;jar\lwjgl_util.jar; -Djava.library.path=native\ org.lwjgl.examples.spaceinvaders.Game + +Project Webpage: www.lwjgl.org +Project Forum: forum.lwjgl.org +Project SVN: https://java-game-lib.svn.sourceforge.net/svnroot/java-game-lib diff --git a/engine/lib/lwjgl/jME3-lwjgl-natives.jar b/engine/lib/lwjgl/jME3-lwjgl-natives.jar new file mode 100644 index 000000000..63564ce61 Binary files /dev/null and b/engine/lib/lwjgl/jME3-lwjgl-natives.jar differ diff --git a/engine/lib/lwjgl/jinput.jar b/engine/lib/lwjgl/jinput.jar new file mode 100644 index 000000000..7c2b6b06f Binary files /dev/null and b/engine/lib/lwjgl/jinput.jar differ diff --git a/engine/lib/lwjgl/lwjgl-debug.jar b/engine/lib/lwjgl/lwjgl-debug.jar new file mode 100644 index 000000000..b1b424fa2 Binary files /dev/null and b/engine/lib/lwjgl/lwjgl-debug.jar differ diff --git a/engine/lib/lwjgl/lwjgl.jar b/engine/lib/lwjgl/lwjgl.jar new file mode 100644 index 000000000..0f5681549 Binary files /dev/null and b/engine/lib/lwjgl/lwjgl.jar differ diff --git a/engine/lib/lwjgl/lwjgl_hidden_switches.text b/engine/lib/lwjgl/lwjgl_hidden_switches.text new file mode 100644 index 000000000..6efed5e7b --- /dev/null +++ b/engine/lib/lwjgl/lwjgl_hidden_switches.text @@ -0,0 +1,25 @@ +LWJGL "Hidden" switches: + +org.lwjgl.opengl.Display.noinput +Do not initialize any controls when creating the display + +org.lwjgl.opengl.Display.nomouse +Do not create the mouse when creating the display + +org.lwjgl.opengl.Display.nokeyboard +Do not create the keyboard when creating the display + +org.lwjgl.util.Debug +Whether to output debug info + +org.lwjgl.util.NoChecks +Whether to disable runtime function/buffer checks and state tracking. + +org.lwjgl.opengl.Display.allowSoftwareOpenGL +Whether to allow creation of a software only opengl context + +org.lwjgl.opengl.Window.undecorated +Whether to create an undecorated window (no title bar) + +org.lwjgl.input.Mouse.allowNegativeMouseCoords +Usually mouse is clamped to 0,0 - setting this to true will cause you to get negative values if dragging outside and below or left of window \ No newline at end of file diff --git a/engine/lib/nblibraries.properties b/engine/lib/nblibraries.properties new file mode 100644 index 000000000..1a39e98ab --- /dev/null +++ b/engine/lib/nblibraries.properties @@ -0,0 +1,55 @@ +libs.CopyLibs.classpath=\ + ${base}/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar +libs.jbullet.classpath=\ + ${base}/jbullet/jbullet.jar:\ + ${base}/jbullet/stack-alloc.jar:\ + ${base}/jbullet/vecmath.jar +libs.jheora.classpath=\ + ${base}/jheora/jheora-jst-debug-0.6.0.jar +libs.jme3-test-data.classpath=\ + ${base}/../src/test-data/ +libs.jogg.classpath=\ + ${base}/jogg/j-ogg-oggd.jar:\ + ${base}/jogg/j-ogg-vorbisd.jar +libs.jogl.classpath=\ + ${base}/jogl/gluegen-rt.jar:\ + ${base}/jogl/jME3-jogl-natives.jar:\ + ${base}/jogl/jogl.jar +libs.jogl2.classpath=\ + ${base}/jogl2/jogl.all.jar;\ + ${base}/jogl2/nativewindow.all.jar;\ + ${base}/jogl2/newt.all.jar;\ + ${base}/jogl2/gluegen-rt.jar +libs.junit_4.classpath=\ + ${base}/junit_4/junit-4.5.jar +libs.junit_4.javadoc=\ + ${base}/junit_4/junit-4.5-api.zip +libs.junit_4.src=\ + ${base}/junit_4/junit-4.5-src.jar +libs.JWSAntTasks.classpath=\ + ${base}/JWSAntTasks/org-netbeans-modules-javawebstart-anttasks.jar +libs.lwjgl.classpath=\ + ${base}/lwjgl/jME3-lwjgl-natives.jar;\ + ${base}/lwjgl/jinput.jar;\ + ${base}/lwjgl/lwjgl.jar +libs.niftygui1.2.classpath=\ + ${base}/niftygui/nifty-1.2-SNAPSHOT.jar;\ + ${base}/niftygui/nifty-default-controls-1.2-SNAPSHOT.jar;\ + ${base}/niftygui/nifty-style-black-1.2-SNAPSHOT.jar;\ + ${base}/niftygui/xmlpull-xpp3-1.1.4c.jar;\ + ${base}/niftygui/nifty-examples-1.2_small.jar +libs.niftygui1.3.classpath=\ + ${base}/niftygui/nifty-1.3-SNAPSHOT.jar;\ + ${base}/niftygui/nifty-default-controls-1.3-SNAPSHOT.jar;\ + ${base}/niftygui/nifty-style-black-1.3-SNAPSHOT.jar;\ + ${base}/niftygui/nifty-style-grey-1.0.jar;\ + ${base}/niftygui/nifty-examples-1.3-SNAPSHOT.jar;\ + ${base}/niftygui/xmlpull-xpp3-1.1.4c.jar;\ + ${base}/niftygui/eventbus-1.4.jar +libs.swing-layout.classpath=\ + ${base}/swing-layout/swing-layout-1.0.4.jar +libs.swing-layout.javadoc=\ + ${base}/swing-layout/swing-layout-1.0.4-doc.zip +libs.swing-layout.src=\ + ${base}/swing-layout/swing-layout-1.0.4-src.zip + diff --git a/engine/lib/niftygui/eventbus-1.4.jar b/engine/lib/niftygui/eventbus-1.4.jar new file mode 100644 index 000000000..a02006f95 Binary files /dev/null and b/engine/lib/niftygui/eventbus-1.4.jar differ diff --git a/engine/lib/niftygui/nifty-1.3-SNAPSHOT.jar b/engine/lib/niftygui/nifty-1.3-SNAPSHOT.jar new file mode 100644 index 000000000..5b107697c Binary files /dev/null and b/engine/lib/niftygui/nifty-1.3-SNAPSHOT.jar differ diff --git a/engine/lib/niftygui/nifty-default-controls-1.3-SNAPSHOT.jar b/engine/lib/niftygui/nifty-default-controls-1.3-SNAPSHOT.jar new file mode 100644 index 000000000..62e9c1ce8 Binary files /dev/null and b/engine/lib/niftygui/nifty-default-controls-1.3-SNAPSHOT.jar differ diff --git a/engine/lib/niftygui/nifty-examples-1.3-SNAPSHOT.jar b/engine/lib/niftygui/nifty-examples-1.3-SNAPSHOT.jar new file mode 100644 index 000000000..ac27eba16 Binary files /dev/null and b/engine/lib/niftygui/nifty-examples-1.3-SNAPSHOT.jar differ diff --git a/engine/lib/niftygui/nifty-style-black-1.3-SNAPSHOT.jar b/engine/lib/niftygui/nifty-style-black-1.3-SNAPSHOT.jar new file mode 100644 index 000000000..69a443402 Binary files /dev/null and b/engine/lib/niftygui/nifty-style-black-1.3-SNAPSHOT.jar differ diff --git a/engine/lib/niftygui/nifty-style-grey-1.0.jar b/engine/lib/niftygui/nifty-style-grey-1.0.jar new file mode 100644 index 000000000..e79967eaa Binary files /dev/null and b/engine/lib/niftygui/nifty-style-grey-1.0.jar differ diff --git a/engine/lib/niftygui/xmlpull-xpp3-1.1.4c.jar b/engine/lib/niftygui/xmlpull-xpp3-1.1.4c.jar new file mode 100644 index 000000000..99b7aeabb Binary files /dev/null and b/engine/lib/niftygui/xmlpull-xpp3-1.1.4c.jar differ diff --git a/engine/lib/swing-layout/swing-layout-1.0.4-doc.zip b/engine/lib/swing-layout/swing-layout-1.0.4-doc.zip new file mode 100644 index 000000000..6ea4f2b37 Binary files /dev/null and b/engine/lib/swing-layout/swing-layout-1.0.4-doc.zip differ diff --git a/engine/lib/swing-layout/swing-layout-1.0.4-src.zip b/engine/lib/swing-layout/swing-layout-1.0.4-src.zip new file mode 100644 index 000000000..ec9836ee0 Binary files /dev/null and b/engine/lib/swing-layout/swing-layout-1.0.4-src.zip differ diff --git a/engine/lib/swing-layout/swing-layout-1.0.4.jar b/engine/lib/swing-layout/swing-layout-1.0.4.jar new file mode 100644 index 000000000..46fe3a2ee Binary files /dev/null and b/engine/lib/swing-layout/swing-layout-1.0.4.jar differ diff --git a/engine/lib/swingext/swing-layout-1.0.3.jar b/engine/lib/swingext/swing-layout-1.0.3.jar new file mode 100644 index 000000000..6e1b43b36 Binary files /dev/null and b/engine/lib/swingext/swing-layout-1.0.3.jar differ diff --git a/engine/master-application.jnlp b/engine/master-application.jnlp new file mode 100644 index 000000000..04731feda --- /dev/null +++ b/engine/master-application.jnlp @@ -0,0 +1,26 @@ + + + ${APPLICATION.TITLE} + ${APPLICATION.VENDOR} + + ${APPLICATION.DESC} + ${APPLICATION.DESC.SHORT} + + + + + + + + + + + + + + + + +

+ + diff --git a/engine/nbproject/build-impl.xml b/engine/nbproject/build-impl.xml new file mode 100644 index 000000000..670444fc4 --- /dev/null +++ b/engine/nbproject/build-impl.xml @@ -0,0 +1,1082 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.core.dir + Must set src.core-data.dir + Must set src.core-plugins.dir + Must set src.terrain.dir + Must set src.networking.dir + Must set src.desktop.dir + Must set src.desktop-fx.dir + Must set src.games.dir + Must set src.jbullet.dir + Must set src.niftygui.dir + Must set src.jogg.dir + Must set src.lwjgl-oal.dir + Must set src.lwjgl-ogl.dir + Must set src.ogre.dir + Must set src.pack.dir + Must set src.jheora.dir + Must set src.test.dir + Must set src.tools.dir + Must set src.xml.dir + Must set test.test.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/nbproject/configs/JWS_generated.properties b/engine/nbproject/configs/JWS_generated.properties new file mode 100644 index 000000000..7a873bf68 --- /dev/null +++ b/engine/nbproject/configs/JWS_generated.properties @@ -0,0 +1,4 @@ +$label=Web Start +$target.debug=jws-debug +$target.run=jws-run +compile.on.save.unsupported.javawebstart=true diff --git a/engine/nbproject/genfiles.properties b/engine/nbproject/genfiles.properties new file mode 100644 index 000000000..743b9a955 --- /dev/null +++ b/engine/nbproject/genfiles.properties @@ -0,0 +1,11 @@ +build.xml.data.CRC32=52f8cb9e +build.xml.script.CRC32=34d4c2f2 +build.xml.stylesheet.CRC32=958a1d3e +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=759acdca +nbproject/build-impl.xml.script.CRC32=10762282 +nbproject/build-impl.xml.stylesheet.CRC32=229523de@1.38.3.45 +nbproject/profiler-build-impl.xml.data.CRC32=aff514c1 +nbproject/profiler-build-impl.xml.script.CRC32=abda56ed +nbproject/profiler-build-impl.xml.stylesheet.CRC32=f10cf54c@1.11.1 diff --git a/engine/nbproject/jnlp-impl.xml b/engine/nbproject/jnlp-impl.xml new file mode 100644 index 000000000..f180fdd71 --- /dev/null +++ b/engine/nbproject/jnlp-impl.xml @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + javaws "${jnlp.file.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/nbproject/profiler-build-impl.xml b/engine/nbproject/profiler-build-impl.xml new file mode 100644 index 000000000..300ea4075 --- /dev/null +++ b/engine/nbproject/profiler-build-impl.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + Must select one file in the IDE or set profile.class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/nbproject/project.properties b/engine/nbproject/project.properties new file mode 100644 index 000000000..25bd84cc8 --- /dev/null +++ b/engine/nbproject/project.properties @@ -0,0 +1,107 @@ +annotation.processing.enabled=false +annotation.processing.enabled.in.editor=false +annotation.processing.run.all.processors=true +ant.customtasks.libs=JWSAntTasks +application.homepage=http://www.jmonkeyengine.com/ +application.title=jMonkeyEngine 3.0 +application.vendor=jMonkeyEngine +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/jMonkeyEngine3.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.src-test-data=src/test-data +includes=** +jar.archive.disabled=${jnlp.enabled} +jar.compress=true +jar.index=${jnlp.enabled} +javac.classpath=\ + ${libs.jogg.classpath}:\ + ${libs.jbullet.classpath}:\ + ${libs.lwjgl.classpath}:\ + ${libs.jheora.classpath}:\ + ${libs.niftygui1.3.classpath}:\ + ${libs.jme3-test-data.classpath} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.5 +javac.target=1.5 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit_4.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle=jMonkeyEngine3 +jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api" +jnlp.applet.class=jme3test.awt.AppHarness +jnlp.applet.height=300 +jnlp.applet.width=300 +jnlp.codebase.type=user +jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/ +jnlp.descriptor=application +jnlp.enabled=false +jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png +jnlp.mixed.code=defaut +jnlp.offline-allowed=true +jnlp.signed=true +main.class=jme3test.TestChooser +manifest.file=MANIFEST.MF +meta.inf.dir=${src.dir}/META-INF +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.core-data.dir=src/core-data +src.core-plugins.dir=src/core-plugins +src.core.dir=src/core +src.desktop-fx.dir=src/desktop-fx +src.desktop.dir=src/desktop +src.games.dir=src/games +src.jbullet.dir=src/jbullet +src.jheora.dir=src/jheora +src.jogg.dir=src/jogg +src.lwjgl-oal.dir=src/lwjgl-oal +src.lwjgl-ogl.dir=src/lwjgl-ogl +src.networking.dir=src\\networking +src.niftygui.dir=src/niftygui +src.ogre.dir=src/ogre +src.pack.dir=src/pack +src.terrain.dir=src/terrain +src.test.dir=src/test +src.tools.dir=src/tools +src.xml.dir=src/xml +test.test.dir=test diff --git a/engine/nbproject/project.xml b/engine/nbproject/project.xml new file mode 100644 index 000000000..8aea04430 --- /dev/null +++ b/engine/nbproject/project.xml @@ -0,0 +1,42 @@ + + + org.netbeans.modules.java.j2seproject + + + + + + + + jMonkeyEngine3 + 1.6.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + ./lib/nblibraries.properties + + + diff --git a/engine/preview-application.html b/engine/preview-application.html new file mode 100644 index 000000000..4834dd87b --- /dev/null +++ b/engine/preview-application.html @@ -0,0 +1,17 @@ + + + + Test page for launching the application via JNLP + + +

Test page for launching the application via JNLP

+ + + + + + diff --git a/engine/quake3level.zip b/engine/quake3level.zip new file mode 100644 index 000000000..291ff1182 Binary files /dev/null and b/engine/quake3level.zip differ diff --git a/engine/src/android/com/jme3/R.java b/engine/src/android/com/jme3/R.java new file mode 100644 index 000000000..af9687431 --- /dev/null +++ b/engine/src/android/com/jme3/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; + +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/app/AndroidHarness.java b/engine/src/android/com/jme3/app/AndroidHarness.java new file mode 100644 index 000000000..d8b0c9a03 --- /dev/null +++ b/engine/src/android/com/jme3/app/AndroidHarness.java @@ -0,0 +1,72 @@ +package com.jme3.app; + +import android.app.Activity; +import android.content.res.Resources; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; +import com.jme3.R; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import com.jme3.system.android.OGLESContext; + +/** + * + * @author Kirill + */ +public class AndroidHarness extends Activity { + + private OGLESContext ctx; + private GLSurfaceView view; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + JmeSystem.setResources(getResources()); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + AppSettings settings = new AppSettings(true); + +// String appClass = getResources().getString(R.string.jme3_appclass); + String appClass = "jme3test.android.Test"; + Application app = null; + try{ + Class clazz = (Class) Class.forName(appClass); + app = clazz.newInstance(); + }catch (Exception ex){ + ex.printStackTrace(); + } + + app.setSettings(settings); + app.start(); + + ctx = (OGLESContext) app.getContext(); + view = ctx.createView(this); + setContentView(view); + } + + @Override + protected void onResume() { + super.onResume(); + view.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + view.onPause(); + } + +// @Override +// protected void onDestroy(){ +// super.onDestroy(); + +// Debug.stopMethodTracing(); +// } + +} 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 000000000..4db44e3dc --- /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 000000000..ee2908e87 --- /dev/null +++ b/engine/src/android/com/jme3/asset/AndroidAssetManager.java @@ -0,0 +1,269 @@ +package com.jme3.asset; + +import com.jme3.asset.plugins.AndroidLocator; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioKey; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.font.BitmapFont; +import com.jme3.font.plugins.BitmapFontLoader; +import com.jme3.material.Material; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.scene.Spatial; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderKey; +import com.jme3.shader.plugins.GLSLLoader; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.plugins.AndroidImageLoader; +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.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AssetManager for Android + * + * @author Kirill Vainer + */ +public final class AndroidAssetManager implements AssetManager { + + private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName()); + + private final AndroidLocator locator = new AndroidLocator(); + private final AndroidImageLoader imageLoader = new AndroidImageLoader(); + private final BinaryImporter modelLoader = new BinaryImporter(); + private final BitmapFontLoader fontLoader = new BitmapFontLoader(); + private final J3MLoader j3mLoader = new J3MLoader(); + private final J3MLoader j3mdLoader = new J3MLoader(); + private final GLSLLoader glslLoader = new GLSLLoader(); + + private final BinaryExporter exporter = new BinaryExporter(); + private final HashMap cache = new HashMap(); + + public AndroidAssetManager(){ + this(false); + } + + public AndroidAssetManager(boolean loadDefaults){ + if (loadDefaults){ +// AssetConfig cfg = new AssetConfig(this); +// InputStream stream = AssetManager.class.getResourceAsStream("Desktop.cfg"); +// try{ +// 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("AndroidAssetManager created."); + } + + public void registerLoader(String loaderClass, String ... extensions){ + } + + public void registerLocator(String rootPath, String locatorClass, String ... extensions){ + } + + private Object tryLoadFromHD(AssetKey key){ + if (!key.getExtension().equals("fnt")) + return null; + + File f = new File("/sdcard/" + key.getName() + ".opt"); + if (!f.exists()) + return null; + + try { + InputStream stream = new FileInputStream(f); + BitmapFont font = (BitmapFont) modelLoader.load(stream, null, null); + stream.close(); + return font; + } catch (IOException ex){ + } + + return null; + } + + private void tryPutToHD(AssetKey key, Object data){ + if (!key.getExtension().equals("fnt")) + return; + + File f = new File("/sdcard/" + key.getName() + ".opt"); + + try { + BitmapFont font = (BitmapFont) data; + OutputStream stream = new FileOutputStream(f); + exporter.save(font, stream); + stream.close(); + } catch (IOException ex){ + } + } + + public Object loadAsset(AssetKey key){ + logger.info("loadAsset(" + key + ")"); + Object asset; +// Object asset = tryLoadFromHD(key); +// if (asset != null) +// return asset; + + if (key.shouldCache()){ + asset = cache.get(key); + if (asset != null) + return key.createClonedInstance(asset); + } + // find resource + AssetInfo info = locator.locate(this, key); + if (info == null){ + logger.log(Level.WARNING, "Cannot locate resource: "+key.getName()); + return null; + } + + String ex = key.getExtension(); + logger.log(Level.INFO, "Loading asset: "+key.getName()); + try{ + if (ex.equals("png") || ex.equals("jpg") + || ex.equals("jpeg") || ex.equals("j3i")){ + Image image; + if (ex.equals("j3i")){ + image = (Image) modelLoader.load(info); + }else{ + image = (Image) imageLoader.load(info); + } + TextureKey tkey = (TextureKey) key; + asset = image; + Texture tex = (Texture) tkey.postProcess(asset); + tex.setMagFilter(Texture.MagFilter.Nearest); + tex.setAnisotropicFilter(0); + if (tex.getMinFilter().usesMipMapLevels()){ + tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap); + }else{ + tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + } + asset = tex; + }else if (ex.equals("j3o")){ + asset = modelLoader.load(info); + }else if (ex.equals("fnt")){ + asset = fontLoader.load(info); + }else if (ex.equals("j3md")){ + asset = j3mdLoader.load(info); + }else if (ex.equals("j3m")){ + asset = j3mLoader.load(info); + }else{ + logger.info("loading asset as glsl shader ..."); + asset = glslLoader.load(info); + // logger.log(Level.WARNING, "No loader registered for type: "+ex); + // return null; + } + + if (key.shouldCache()) + cache.put(key, asset); + +// tryPutToHD(key, asset); + + return key.createClonedInstance(asset); + } catch (Exception e){ + logger.log(Level.WARNING, "Failed to load resource: "+key.getName(), e); + } + return null; + } + + public AssetInfo locateAsset(AssetKey key){ + AssetInfo info = locator.locate(this, key); + if (info == null){ + logger.log(Level.WARNING, "Cannot locate resource: "+key.getName()); + return null; + } + return info; + } + + + + + public Object loadAsset(String name) { + return loadAsset(new AssetKey(name)); + } + + public Spatial loadModel(String name) { + return (Spatial) loadAsset(name); + } + + public Material loadMaterial(String name) { + return (Material) loadAsset(name); + } + + public BitmapFont loadFont(String name){ + return (BitmapFont) loadAsset(name); + } + + public Texture loadTexture(TextureKey key){ + return (Texture) loadAsset(key); + } + + public Texture loadTexture(String name){ + return loadTexture(new TextureKey(name, false)); + } + + public Shader loadShader(ShaderKey key){ + logger.info("loadShader(" + key + ")"); + + String vertName = key.getVertName(); + String fragName = key.getFragName(); + + String vertSource = (String) loadAsset(new AssetKey(vertName)); + String fragSource = (String) loadAsset(new AssetKey(fragName)); + + Shader 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()); + + logger.info("returing shader: [" + s + "]"); + return s; + } + + + public void registerLocator(String rootPath, String locatorClassName) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public AudioData loadAudio(AudioKey key) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public AudioData loadAudio(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public Spatial loadModel(ModelKey key) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /* new */ + + private AssetEventListener eventListener = null; + + public void setAssetEventListener(AssetEventListener listener){ + eventListener = listener; + } + + public void registerLocator(String rootPath, Class locatorClass){ + logger.warning("not implemented."); + } + + public void registerLoader(Class loader, String ... extensions){ + logger.warning("not implemented."); + } + + + + +} 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 000000000..adbe7603d --- /dev/null +++ b/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java @@ -0,0 +1,62 @@ +package com.jme3.asset.plugins; + +import android.content.res.AssetManager; +import android.content.res.Resources; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLocator; +import com.jme3.system.JmeSystem; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +public class AndroidLocator implements AssetLocator { + + private static final Logger logger = Logger.getLogger(AndroidLocator.class.getName()); + private Resources resources; + private AssetManager androidManager; + + private class AndroidAssetInfo extends AssetInfo { + + private final InputStream in; + + public AndroidAssetInfo(com.jme3.asset.AssetManager manager, AssetKey key, + InputStream in){ + super(manager, key); + this.in = in; + } + + @Override + public InputStream openStream() { + return in; + } + } + + + public AndroidLocator(){ + resources = JmeSystem.getResources(); + androidManager = resources.getAssets(); + } + + public void setRootPath(String rootPath) { + } + + public AssetInfo locate(com.jme3.asset.AssetManager manager, AssetKey key) { + InputStream in = null; + try { + in = androidManager.open(key.getName()); + if (in == null) + return null; + + return new AndroidAssetInfo(manager, key, in); + } catch (IOException ex) { + if (in != null) + try { + in.close(); + } catch (IOException ex1) { + } + } + return null; + } + +} 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 000000000..3be23f644 --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidInput.java @@ -0,0 +1,448 @@ +package com.jme3.input.android; + +import java.util.List; +import java.util.ArrayList; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; + +public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput { + + private RawInputListener listener; + private int lastX = -1, lastY = -1; + + private static final char[] ANDROID_TO_JME_CHR = { + 0x0,// unknown + 0x0,// soft left + 0x0,// soft right + 0x0,// home + 0x0,// back + 0x0,// call + 0x0,// endcall + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '*', + '#', + 0x0,//dpad_up + 0x0,//dpad_down + 0x0,//dpad_left + 0x0,//dpad_right + 0x0,//dpad_center + 0x0,//volume up + 0x0,//volume down + 0x0,//power + 0x0,//camera + 0x0,//clear + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ',', + '.', + + 0x0,//left alt + 0x0,//right alt + 0x0,//left ctrl + 0x0,//right ctrl + +// 0x0,//fn +// 0x0,//cap + + '\t', + ' ', + 0x0,//sym(bol) + 0x0,//explorer + 0x0,//envelope + '\n',//newline + 0x0,//delete + '`', + '-', + '=', + '[', + ']', + '\\',//backslash + ';', + '\'',//apostrophe + '/',//slash + '@',//at + 0x0,//num + 0x0,//headset hook + 0x0,//focus + 0x0, + 0x0,//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 + }; + + 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 + }; + +// private int[] keyMap = { +// 0x0, +// KeyEvent.KEYCODE_BACK, // ESC key +// +// KeyEvent.KEYCODE_1, +// KeyEvent.KEYCODE_2, +// KeyEvent.KEYCODE_3, +// KeyEvent.KEYCODE_4, +// KeyEvent.KEYCODE_5, +// KeyEvent.KEYCODE_6, +// KeyEvent.KEYCODE_7, +// KeyEvent.KEYCODE_8, +// KeyEvent.KEYCODE_9, +// KeyEvent.KEYCODE_0, +// KeyEvent.KEYCODE_MINUS, +// KeyEvent.KEYCODE_EQUALS, +// KeyEvent.KEYCODE_BACK, +// KeyEvent.KEYCODE_TAB, +// KeyEvent.KEYCODE_Q, +// KeyEvent.KEYCODE_W, +// KeyEvent.KEYCODE_E, +// KeyEvent.KEYCODE_R, +// KeyEvent.KEYCODE_T, +// KeyEvent.KEYCODE_Y, +// KeyEvent.KEYCODE_U, +// KeyEvent.KEYCODE_I, +// KeyEvent.KEYCODE_O, +// KeyEvent.KEYCODE_P, +// KeyEvent.KEYCODE_LEFT_BRACKET, +// KeyEvent.KEYCODE_RIGHT_BRACKET, +// KeyEvent.KEYCODE_ENTER, +// KeyEvent.KEYCODE_SOFT_LEFT, // Left Ctrl +// KeyEvent.KEYCODE_A, +// KeyEvent.KEYCODE_S, +// KeyEvent.KEYCODE_D, +// KeyEvent.KEYCODE_F, +// KeyEvent.KEYCODE_G, +// KeyEvent.KEYCODE_H, +// KeyEvent.KEYCODE_J, +// KeyEvent.KEYCODE_K, +// KeyEvent.KEYCODE_L, +// KeyEvent.KEYCODE_SEMICOLON, +// KeyEvent.KEYCODE_APOSTROPHE, +// KeyEvent.KEYCODE_GRAVE, +// KeyEvent.KEYCODE_SHIFT_LEFT, +// KeyEvent.KEYCODE_BACKSLASH, +// KeyEvent.KEYCODE_Z, +// KeyEvent.KEYCODE_X, +// KeyEvent.KEYCODE_C, +// KeyEvent.KEYCODE_V, +// KeyEvent.KEYCODE_B, +// KeyEvent.KEYCODE_N, +// KeyEvent.KEYCODE_M, +// +// KeyEvent.KEYCODE_COMMA, +// KeyEvent.KEYCODE_PERIOD, +// KeyEvent.KEYCODE_SLASH, +// KeyEvent.KEYCODE_SHIFT_RIGHT, +// KeyEvent.KEYCODE_STAR, +// +// KeyEvent.KEYCODE_ALT_LEFT, +// KeyEvent.KEYCODE_SPACE, +// +// 0x0, // no caps lock +// +// 0x0, // F1 +// 0x0, // F2 +// 0x0, // F3 +// 0x0, // F4 +// 0x0, // F5 +// 0x0, // F6 +// 0x0, // F7 +// 0x0, // F8 +// 0x0, // F9 +// 0x0, // F10 +// +// KeyEvent.KEYCODE_NUM, +// 0x0, // scroll lock +// +// 0x0, // numpad7 +// 0x0, // numpad8 +// 0x0, // numpad9 +// +// KeyEvent. +// } + + public AndroidInput(Context ctx, AttributeSet attribs){ + super(ctx, attribs); + } + + public AndroidInput(Context ctx){ + super(ctx); + } + + @Override + public boolean onTouchEvent(MotionEvent motionEvent){ + int newX = getWidth() - (int) motionEvent.getX(); + int newY = (int) motionEvent.getY(); + + + switch (motionEvent.getAction()){ + case MotionEvent.ACTION_DOWN: + MouseButtonEvent btn = new MouseButtonEvent(0, true, newX, newY); + btn.setTime(motionEvent.getEventTime()); + processEvent(btn); + // listener.onMouseButtonEvent(btn); + lastX = -1; + lastY = -1; + return true; + case MotionEvent.ACTION_UP: + MouseButtonEvent btn2 = new MouseButtonEvent(0, false, newX, newY); + btn2.setTime(motionEvent.getEventTime()); + processEvent(btn2); + // listener.onMouseButtonEvent(btn2); + lastX = -1; + lastY = -1; + return true; + case MotionEvent.ACTION_MOVE: + // int newX = getWidth() - (int) motionEvent.getX(); + // int newY = (int) motionEvent.getY(); + int dx; + int dy; + if (lastX != -1){ + dx = newX - lastX; + dy = newY - lastY; + }else{ + dx = 0; + dy = 0; + } + lastX = newX; + lastY = newY; + MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0); + mot.setTime(motionEvent.getEventTime()); + processEvent(mot); + //listener.onMouseMotionEvent(mot); + try{ + Thread.sleep(15); + } catch (InterruptedException ex) { + } + return true; + } + return false; + } + + @Override + public boolean onKeyDown (int keyCode, KeyEvent event) { + int jmeCode = ANDROID_TO_JME[keyCode]; + String str = event.getCharacters(); + char c = str != null && str.length() > 0 ? str.charAt(0) : 0x0; + KeyInputEvent evt = new KeyInputEvent(jmeCode, c, true, false); + processEvent(evt); + // listener.onKeyEvent(evt); + return false; + } + + @Override + public boolean onKeyUp (int keyCode, KeyEvent event) { + int jmeCode = ANDROID_TO_JME[keyCode]; + String str = event.getCharacters(); + char c = str != null && str.length() > 0 ? str.charAt(0) : 0x0; + KeyInputEvent evt = new KeyInputEvent(jmeCode, c, false, false); + processEvent(evt); + //listener.onKeyEvent(evt); + return false; + } + + public void setCursorVisible(boolean visible){ + } + + public int getButtonCount(){ + return 255; + } + + public void initialize() { + } + + public void update() { + generateEvents(); + } + + public void destroy() { + } + + public boolean isInitialized() { + return true; + } + + // XXX: android does not have an Event interface? + private List currentEvents = new ArrayList(); + + private final static int MAX_EVENTS = 1024; + + private void processEvent(Object event) { + synchronized (currentEvents) { + if (currentEvents.size() < MAX_EVENTS) + currentEvents.add(event); + } + } + + private void generateEvents() { + synchronized (currentEvents) { + for (Object event: currentEvents) { + if (event instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent) event); + } else if (event instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent) event); + } else if (event instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent) event); + } + } + currentEvents.clear(); + } + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + +} diff --git a/engine/src/android/com/jme3/renderer/android/OGLESRenderer.java b/engine/src/android/com/jme3/renderer/android/OGLESRenderer.java new file mode 100644 index 000000000..7d7244a8b --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/OGLESRenderer.java @@ -0,0 +1,991 @@ +package com.jme3.renderer.android; + +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GLObjectManager; +import com.jme3.renderer.IDList; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.Renderer; +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 java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.Collection; +import java.util.EnumSet; +import java.util.logging.Logger; +import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11Ext; + +public final class OGLESRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(OGLESRenderer.class.getName()); + + private Matrix4f worldMatrix = new Matrix4f(); + private Matrix4f viewMatrix = new Matrix4f(); + private Matrix4f projMatrix = new Matrix4f(); + private FloatBuffer fb16 = BufferUtils.createFloatBuffer(16); + private float[] fa16 = new float[16]; + private IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); + private GL10 gl; + private GL11 gl11; + private GL11Ext glExt; + + private RenderContext context = new RenderContext(); + private GLObjectManager objManager = new GLObjectManager(); + + private EnumSet caps = EnumSet.noneOf(Caps.class); + private boolean powerOf2 = false; + + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + + public OGLESRenderer(GL10 gl){ + setGL(gl); + } + + public void setGL(GL10 gl){ + this.gl = gl; + if (gl instanceof GL11){ + gl11 = (GL11) gl; + if (gl instanceof GL11Ext){ + glExt = (GL11Ext) gl; + } + } + } + + public void initialize(){ + logger.info("Vendor: "+gl.glGetString(gl.GL_VENDOR)); + logger.info("Renderer: "+gl.glGetString(gl.GL_RENDERER)); + logger.info("Version: "+gl.glGetString(gl.GL_VERSION)); + + String extensions = gl.glGetString(gl.GL_EXTENSIONS); + if (extensions.contains("GL_OES_texture_npot")) + powerOf2 = true; + + applyRenderState(RenderState.DEFAULT); +// gl.glClearDepthf(1.0f); + gl.glDisable(GL10.GL_DITHER); + gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); + } + + public Statistics getStatistics() { + return null; + } + + public void setClipRect(int x, int y, int width, int height) { + } + + public void clearClipRect() { + } + + public void resetGLObjects() { + } + + public EnumSet getCaps() { + return caps; + } + + public void setBackgroundColor(ColorRGBA color) { + gl.glClearColor(color.r, color.g, color.b, color.a); + } + + public void cleanup(){ + objManager.deleteAllObjects(this); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) bits = gl.GL_COLOR_BUFFER_BIT; + if (depth) bits |= gl.GL_DEPTH_BUFFER_BIT; + if (stencil) bits |= gl.GL_STENCIL_BUFFER_BIT; + if (bits != 0) gl.glClear(bits); + } + + public void applyRenderState(RenderState state){ + // TODO: is wireframe supported under OGL ES? + +// if (state.isWireframe() && !context.wireframe){ +// gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE); +// context.wireframe = true; +// }else if (!state.isWireframe() && context.wireframe){ +// gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL); +// context.wireframe = false; +// } + + if (state.isDepthTest() && !context.depthTestEnabled){ + gl.glEnable(gl.GL_DEPTH_TEST); + gl.glDepthFunc(gl.GL_LEQUAL); + context.depthTestEnabled = true; + }else if (!state.isDepthTest() && context.depthTestEnabled){ + gl.glDisable(gl.GL_DEPTH_TEST); + context.depthTestEnabled = false; + } + if (state.isAlphaTest() && !context.alphaTestEnabled){ + gl.glEnable(gl.GL_ALPHA_TEST); + gl.glAlphaFunc(gl.GL_GREATER, state.getAlphaFallOff()); + context.alphaTestEnabled = true; + }else if (!state.isAlphaTest() && context.alphaTestEnabled){ + gl.glDisable(gl.GL_ALPHA_TEST); + context.alphaTestEnabled = false; + } + if (state.isDepthWrite() && !context.depthWriteEnabled){ + gl.glDepthMask(true); + context.depthWriteEnabled = true; + }else if (!state.isDepthWrite() && context.depthWriteEnabled){ + gl.glDepthMask(false); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled){ + gl.glColorMask(true,true,true,true); + context.colorWriteEnabled = true; + }else if (!state.isColorWrite() && context.colorWriteEnabled){ + gl.glColorMask(false,false,false,false); + context.colorWriteEnabled = false; + } + if (state.isPolyOffset()){ + if (!context.polyOffsetEnabled){ + gl.glEnable(gl.GL_POLYGON_OFFSET_FILL); + gl.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){ + gl.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + }else{ + if (context.polyOffsetEnabled){ + gl.glDisable(gl.GL_POLYGON_OFFSET_FILL); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode){ + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) + gl.glDisable(gl.GL_CULL_FACE); + else + gl.glEnable(gl.GL_CULL_FACE); + + switch (state.getFaceCullMode()){ + case Off: + break; + case Back: + gl.glCullFace(gl.GL_BACK); + break; + case Front: + gl.glCullFace(gl.GL_FRONT); + break; + case FrontAndBack: + gl.glCullFace(gl.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) + gl.glDisable(gl.GL_BLEND); + else + gl.glEnable(gl.GL_BLEND); + + switch (state.getBlendMode()){ + case Off: + break; + case Additive: + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE); + break; + case Alpha: + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + gl.glBlendFunc(gl.GL_DST_COLOR, gl.GL_ZERO); + break; + case ModulateX2: + gl.glBlendFunc(gl.GL_DST_COLOR, gl.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: "+ + state.getBlendMode()); + } + + context.blendMode = state.getBlendMode(); + } + } + + public void onFrame() { + objManager.deleteUnused(this); + } + + public void setDepthRange(float start, float end) { + gl.glDepthRangef(start, end); + } + + public void setViewPort(int x, int y, int width, int height){ + gl.glViewport(x, y, width, height); + vpX = x; + vpY = y; + vpW = width; + vpH = height; + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix){ + this.viewMatrix.set(viewMatrix); + this.projMatrix.set(projMatrix); + + if (context.matrixMode != gl.GL_PROJECTION){ + gl.glMatrixMode(gl.GL_PROJECTION); + context.matrixMode = gl.GL_PROJECTION; + } + + projMatrix.fillFloatArray(fa16, true); + gl.glLoadMatrixf(fa16, 0); + +// gl.glMatrixMode(gl.GL_MODELVIEW); +// gl.glLoadIdentity(); +// gl.glLoadMatrixf(storeMatrix(viewMatrix, fb16)); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + this.worldMatrix.set(worldMatrix); + + if (context.matrixMode != gl.GL_MODELVIEW){ + gl.glMatrixMode(gl.GL_MODELVIEW); + context.matrixMode = gl.GL_MODELVIEW; + } + + viewMatrix.fillFloatArray(fa16, true); + gl.glLoadMatrixf(fa16, 0); + worldMatrix.fillFloatArray(fa16, true); + gl.glMultMatrixf(fa16, 0); + } + + public void setMatrixPalette(Matrix4f[] offsetMatrices){ + if (glExt == null) + throw new UnsupportedOperationException("Requires GL_OES_compressed_paletted_texture"); + + if (context.matrixMode != glExt.GL_MATRIX_PALETTE_OES){ + gl.glMatrixMode(glExt.GL_MATRIX_PALETTE_OES); + context.matrixMode = glExt.GL_MATRIX_PALETTE_OES; + } + + for (int i = 0; i < offsetMatrices.length; i++){ + Matrix4f offsetMat = offsetMatrices[i]; + glExt.glCurrentPaletteMatrixOES(i); + + offsetMat.fillFloatArray(fa16, true); + gl.glLoadMatrixf(fa16, 0); + } + } + + public void setLighting(LightList list) { + if (list.size() == 0) { + // turn off lighting + gl.glDisable(gl.GL_LIGHTING); + return; + } + + gl.glEnable(gl.GL_LIGHTING); + gl.glShadeModel(gl.GL_SMOOTH); + + float[] temp = new float[4]; + + // reset model view to specify + // light positions in world space + // instead of model space + gl.glPushMatrix(); + gl.glLoadIdentity(); + + for (int i = 0; i < list.size()+1; i++){ + if (list.size() <= i){ + // goes beyond the num lights we need + // disable it + gl.glDisable(gl.GL_LIGHT0 + i); + break; + } + + Light l = list.get(i); + int lightId = gl.GL_LIGHT0 + i; + + ColorRGBA color = l.getColor(); + color.toArray(temp); + + gl.glEnable(lightId); + gl.glLightfv(lightId, gl.GL_DIFFUSE, temp, 0); + gl.glLightfv(lightId, gl.GL_SPECULAR, temp, 0); + + ColorRGBA.Black.toArray(temp); + gl.glLightfv(lightId, gl.GL_AMBIENT, temp, 0); + + switch (l.getType()){ + case Directional: + DirectionalLight dl = (DirectionalLight) l; + dl.getDirection().toArray(temp); + temp[3] = 0f; // marks to GL its a directional light + gl.glLightfv(lightId, gl.GL_POSITION, temp, 0); + break; + case Point: + PointLight pl = (PointLight) l; + pl.getPosition().toArray(temp); + temp[3] = 1f; // marks to GL its a point light + gl.glLightfv(lightId, gl.GL_POSITION, temp, 0); + break; + } + + } + + // restore modelview to original value + gl.glPopMatrix(); + } + + public void updateShaderSourceData(ShaderSource source) { + } + + public void deleteShaderSource(ShaderSource source) { + } + + public void updateShaderData(Shader shader) { + } + + public void setShader(Shader shader) { + } + + public void deleteShader(Shader shader) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + } + + public void setFrameBuffer(FrameBuffer fb) { + } + + public void updateFrameBuffer(FrameBuffer fb) { + } + + public void deleteFrameBuffer(FrameBuffer fb) { + } + + /** + * Warning: documentation states that this method returns data in BGRA format, + * it actually returns data in RGBA format. + * @param fb + * @param byteBuf + */ + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + if (fb == null){ + gl.glReadPixels(vpX, vpY, vpW, vpH, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, byteBuf); + }else{ + throw new UnsupportedOperationException(); + } + } + + private int convertTextureType(Texture.Type type){ + switch (type){ + case TwoDimensional: + return gl.GL_TEXTURE_2D; + default: + throw new UnsupportedOperationException("Unknown texture type: "+type); + } + } + + private int convertMagFilter(Texture.MagFilter filter){ + switch (filter){ + case Bilinear: + return gl.GL_LINEAR; + case Nearest: + return gl.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: "+filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter){ + switch (filter){ + case Trilinear: + return gl.GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return gl.GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return gl.GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return gl.GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return gl.GL_LINEAR; + case NearestNoMipMaps: + return gl.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: "+filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode){ + switch (mode){ +// case BorderClamp: +// return gl.GL_CLAMP_TO_BORDER; +// case Clamp: +// return gl.GL_CLAMP; + case EdgeClamp: + return gl.GL_CLAMP_TO_EDGE; + case Repeat: + return gl.GL_REPEAT; +// case MirroredRepeat: +// return gl.GL_MIRRORED_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: "+mode); + } + } + + public void updateTextureData(Texture tex){ + int texId = tex.getId(); + if (texId == -1){ + // create texture + gl.glGenTextures(1, intBuf1); + texId = intBuf1.get(0); + tex.setId(texId); + objManager.registerForCleanup(tex); + } + + // bind texture + int target = convertTextureType(tex.getType()); + if (context.boundTextures[0] != tex){ + if (context.boundTextureUnit != 0){ + gl.glActiveTexture(gl.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + + gl.glBindTexture(target, texId); + context.boundTextures[0] = tex; + } + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + gl.glTexParameterx(target, gl.GL_TEXTURE_MIN_FILTER, minFilter); + gl.glTexParameterx(target, gl.GL_TEXTURE_MAG_FILTER, magFilter); + + // repeat modes + switch (tex.getType()){ + case TwoDimensional: + gl.glTexParameterx(target, gl.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + // fall down here is intentional.. +// case OneDimensional: + gl.glTexParameterx(target, gl.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: "+tex.getType()); + } + + Image img = tex.getImage(); + if (img != null){ + boolean generateMips = false; + if (!img.hasMipmaps() && tex.getMinFilter().usesMipMapLevels()){ + // No pregenerated mips available, + // generate from base level if required + if (gl11 != null){ + gl.glTexParameterx(target, GL11.GL_GENERATE_MIPMAP, gl.GL_TRUE); + }else{ + generateMips = true; + } + } + + TextureUtil.uploadTexture(gl, img, tex.getImageDataIndex(), generateMips, powerOf2); + } + + tex.clearUpdateNeeded(); + } + + private void checkTexturingUsed(){ + IDList textureList = context.textureIndexList; + // old mesh used texturing, new mesh doesn't use it + // should actually go through entire oldLen and + // disable texturing for each unit.. but that's for later. + if (textureList.oldLen > 0 && textureList.newLen == 0){ + gl.glDisable(gl.GL_TEXTURE_2D); + } + } + + public void setTexture(int unit, Texture tex){ + if (tex.isUpdateNeeded()) + updateTextureData(tex); + + int texId = tex.getId(); + assert texId != -1; + + Texture[] textures = context.boundTextures; + + int type = convertTextureType(tex.getType()); + if (!context.textureIndexList.moveToNew(unit)){ + if (context.boundTextureUnit != unit){ + gl.glActiveTexture(gl.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + gl.glEnable(type); + } + + if (textures[unit] != tex){ + if (context.boundTextureUnit != unit){ + gl.glActiveTexture(gl.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + gl.glBindTexture(type, texId); + textures[unit] = tex; + } + } + + public void clearTextureUnits(){ + IDList textureList = context.textureIndexList; + Texture[] textures = context.boundTextures; + for (int i = 0; i < textureList.oldLen; i++){ + int idx = textureList.oldList[i]; + + if (context.boundTextureUnit != idx){ + gl.glActiveTexture(gl.GL_TEXTURE0 + idx); + context.boundTextureUnit = idx; + } + gl.glDisable(convertTextureType(textures[idx].getType())); + textures[idx] = null; + } + context.textureIndexList.copyNewToOld(); + } + + public void deleteTexture(Texture tex){ + int texId = tex.getId(); + if (texId != -1){ + intBuf1.put(0, texId); + intBuf1.position(0).limit(1); + gl.glDeleteTextures(1, intBuf1); + tex.resetObject(); + } + } + + private int convertUsage(Usage usage){ + switch (usage){ + case Static: + return gl11.GL_STATIC_DRAW; + case Dynamic: + case Stream: + return gl11.GL_DYNAMIC_DRAW; + default: + throw new RuntimeException("Unknown usage type: "+usage); + } + } + + public void updateBufferData(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId == -1){ + // create buffer + gl11.glGenBuffers(1, intBuf1); + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerForCleanup(vb); + } + + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index){ + target = gl11.GL_ELEMENT_ARRAY_BUFFER; + if (context.boundElementArrayVBO != bufId){ + gl11.glBindBuffer(target, bufId); + context.boundElementArrayVBO = bufId; + } + }else{ + target = gl11.GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId){ + gl11.glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + Buffer data = vb.getData(); + data.rewind(); + + gl11.glBufferData(target, + data.capacity() * vb.getFormat().getComponentSize(), + data, + usage); + + 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); + gl11.glDeleteBuffers(1, intBuf1); + vb.resetObject(); + } + } + + private int convertArrayType(VertexBuffer.Type type){ + switch (type){ + case Position: + return gl.GL_VERTEX_ARRAY; + case Normal: + return gl.GL_NORMAL_ARRAY; + case TexCoord: + return gl.GL_TEXTURE_COORD_ARRAY; + case Color: + return gl.GL_COLOR_ARRAY; + default: + return -1; // unsupported + } + } + + private int convertVertexFormat(VertexBuffer.Format fmt){ + switch (fmt){ + case Byte: + return gl.GL_BYTE; + case Float: + return gl.GL_FLOAT; + case Short: + return gl.GL_SHORT; + case UnsignedByte: + return gl.GL_UNSIGNED_BYTE; + case UnsignedShort: + return gl.GL_UNSIGNED_SHORT; + case Int: + return gl.GL_FIXED; + default: + throw new UnsupportedOperationException("Unrecognized vertex format: "+fmt); + } + } + + private int convertElementMode(Mesh.Mode mode){ + switch (mode){ + case Points: + return gl.GL_POINTS; + case Lines: + return gl.GL_LINES; + case LineLoop: + return gl.GL_LINE_LOOP; + case LineStrip: + return gl.GL_LINE_STRIP; + case Triangles: + return gl.GL_TRIANGLES; + case TriangleFan: + return gl.GL_TRIANGLE_FAN; + case TriangleStrip: + return gl.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: "+mode); + } + } + + private void setVertexAttribVBO(VertexBuffer vb, VertexBuffer idb){ + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) + return; // unsupported + + if (vb.isUpdateNeeded() && idb == null) + updateBufferData(vb); + + int bufId = idb != null ? idb.getId() : vb.getId(); + if (context.boundArrayVBO != bufId){ + gl11.glBindBuffer(gl11.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + } + + gl.glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal){ + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled){ + gl.glEnable(gl.GL_NORMALIZE); + context.normalizeEnabled = true; + }else if (!vb.isNormalized() && context.normalizeEnabled){ + gl.glDisable(gl.GL_NORMALIZE); + context.normalizeEnabled = false; + } + } + + int comps = vb.getNumComponents(); + int type = convertVertexFormat(vb.getFormat()); + + switch (vb.getBufferType()){ + case Position: + gl11.glVertexPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + case Normal: + gl11.glNormalPointer(type, vb.getStride(), vb.getOffset()); + break; + case Color: + gl11.glColorPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + case TexCoord: + gl11.glTexCoordPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + } + } + + private void drawTriangleListVBO(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){ + gl11.glBindBuffer(gl11.GL_ELEMENT_ARRAY_BUFFER, bufId); + context.boundElementArrayVBO = bufId; + } + + if (mesh.getMode() == Mode.Hybrid){ + 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]; + gl11.glDrawElements(elMode, + elementLength, + fmt, + curOffset); + curOffset += elementLength * elSize; + } + }else{ + gl11.glDrawElements(convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertVertexFormat(indexBuf.getFormat()), + 0); + } + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) + return; // unsupported + + gl.glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal){ + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled){ + gl.glEnable(gl.GL_NORMALIZE); + context.normalizeEnabled = true; + }else if (!vb.isNormalized() && context.normalizeEnabled){ + gl.glDisable(gl.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.clear(); + data.position(vb.getOffset()); + + switch (vb.getBufferType()){ + case Position: + gl.glVertexPointer(comps, type, vb.getStride(), data); + break; + case Normal: + gl.glNormalPointer(type, vb.getStride(), data); + break; + case Color: + gl.glColorPointer(comps, type, vb.getStride(), data); + break; + case TexCoord: + gl.glTexCoordPointer(comps, type, vb.getStride(), data); + break; + } + } + + public void setVertexAttrib(VertexBuffer vb){ + setVertexAttrib(vb, null); + } + + public void clearVertexAttribs() { + for (int i = 0; i < 16; i++){ + VertexBuffer vb = context.boundAttribs[i]; + if (vb != null){ + int arrayType = convertArrayType(vb.getBufferType()); + gl.glDisableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = null; + } + } + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + Mesh.Mode mode = mesh.getMode(); + + 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 = 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); + gl.glDrawElements(elMode, + elementLength, + fmt, + indexData); + curOffset += elementLength; + } + }else{ + gl.glDrawElements(convertElementMode(mode), + indexData.capacity(), + convertVertexFormat(indexBuf.getFormat()), + indexData); + } + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + IntMap buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0){ + indices = mesh.getLodLevel(lod); + }else{ + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry entry : buffers){ + VertexBuffer vb = entry.getValue(); + + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly) // ignore cpu-only buffers + continue; + + if (vb.getBufferType() == Type.Index){ + indices = vb; + }else{ + if (vb.getStride() == 0){ + // not interleaved + setVertexAttrib(vb); + }else{ + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + if (indices != null){ + drawTriangleList(indices, mesh, count); + }else{ + gl.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshVBO(Mesh mesh, int lod, int count){ + VertexBuffer indices = null; + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()){ + updateBufferData(interleavedData); + } + IntMap buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0){ + indices = mesh.getLodLevel(lod); + }else{ + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry 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 + setVertexAttribVBO(vb, null); + }else{ + // interleaved + setVertexAttribVBO(vb, interleavedData); + } + } + + if (indices != null){ + drawTriangleListVBO(indices, mesh, count); + }else{ + gl.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + public void renderMesh(Mesh mesh, int lod, int count){ + // check if texturing is used for new model, if not + // disable texturing entirely. + checkTexturingUsed(); + if (gl11 != null){ + // use vbo + renderMeshVBO(mesh, lod, count); + }else{ + // use vertex arrays + renderMeshDefault(mesh, lod, count); + } + } + +} 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 000000000..6846166b1 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java @@ -0,0 +1,2848 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 com.jme3.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GLObjectManager; +import com.jme3.renderer.IDList; +import com.jme3.renderer.Renderer; +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.renderer.RenderContext; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.Statistics; +import com.jme3.scene.Mesh.Mode; +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 java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/* +//import org.lwjgl.opengl.ARBGeometryShader4; +//import org.lwjgl.opengl.ARBHalfFloatVertex; +//import org.lwjgl.opengl.ARBVertexArrayObject; +//import org.lwjgl.opengl.ARBHalfFloatVertex; +//import org.lwjgl.opengl.ARBVertexArrayObject; +import org.lwjgl.opengl.ARBDrawBuffers; +//import org.lwjgl.opengl.ARBDrawInstanced; +import org.lwjgl.opengl.ARBDrawInstanced; +import org.lwjgl.opengl.ARBMultisample; +import org.lwjgl.opengl.ContextCapabilities; +import org.lwjgl.opengl.EXTTextureArray; +import org.lwjgl.opengl.EXTTextureFilterAnisotropic; +import org.lwjgl.opengl.GLContext; +import org.lwjgl.opengl.NVHalfFloat; + +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 static org.lwjgl.opengl.EXTFramebufferObject.*; +import static org.lwjgl.opengl.EXTFramebufferMultisample.*; +import static org.lwjgl.opengl.EXTFramebufferBlit.*; +import org.lwjgl.opengl.ARBShaderObjects.*; +import org.lwjgl.opengl.ARBVertexArrayObject; +//import static org.lwjgl.opengl.ARBDrawInstanced.*; +*/ + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.opengles.GL10; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.opengl.GLES10; +import android.opengl.GLES11; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.os.SystemClock; +import android.util.Log; + + + +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 GLObjectManager objManager = new GLObjectManager(); + private final EnumSet 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 powerOf2 = false; + private boolean verboseLogging = false; + private boolean useVBO = true; + + + public OGLESShaderRenderer(GL10 gl) { + this.gl = gl; + } + + public void setUseVA(boolean value) { + logger.info("use_VBO [" + useVBO + "] -> [" + (!value) + "]"); + useVBO = !value; + } + + public void setVerboseLogging(boolean value) { + logger.info("verboseLogging [" + 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(); + } + + public Statistics getStatistics(){ + return statistics; + } + + public EnumSet getCaps(){ + return caps; + } + + public void initialize(){ + + logger.info("Vendor: " + GLES20.glGetString(GLES20.GL_VENDOR)); + logger.info("Renderer: " + GLES20.glGetString(GLES20.GL_RENDERER)); + logger.info("Version: " + GLES20.glGetString(GLES20.GL_VERSION)); + + String shadingLanguageVersion = GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION); + logger.info("GLES20.Shading Language Version: " + shadingLanguageVersion); + +/* + 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); + } +*/ + String versionStr = GLES20.glGetString(GLES20.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 = GLES20.glGetIntegeri(GLES20.GL_DRAW_BUFFER); +// initialReadBuf = GLES20.glGetIntegeri(GLES20.GL_READ_BUFFER); + + int spaceIdx = versionStr.lastIndexOf(" "); + if (spaceIdx >= 1) + versionStr = versionStr.substring(spaceIdx, versionStr.length()); + + 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.info("Force-adding GLSL100 support, since OpenGL is supported."); + caps.add(Caps.GLSL100); + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); + vertexTextureUnits = intBuf16.get(0); + logger.info("VTF Units: " + vertexTextureUnits); + if (vertexTextureUnits > 0) + caps.add(Caps.VertexTextureFetch); + + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); + fragTextureUnits = intBuf16.get(0); + logger.info("Texture Units: " + 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.info("Vertex Attributes: " + 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.info("Subpixel Bits: " + 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.info("Maximum Texture Resolution: " + maxTexSize); + + GLES20.glGetIntegerv(GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); + maxCubeTexSize = intBuf16.get(0); + logger.info("Maximum CubeMap Resolution: " + 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.info("GL_EXTENSIONS: " + extensions); + + 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)"); + + GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); + +// checkGLError(); + + logger.log(Level.INFO, "Caps: " + caps); + } + + 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.info("GLES20.glDepthRangef(" + 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.info("GLES20.glClear(color=" + color + ", depth=" + depth + ", stencil=" + stencil + ")"); + GLES20.glClear(bits); checkGLError(); + } + } + + public void setBackgroundColor(ColorRGBA color){ + if (verboseLogging) + logger.info("GLES20.glClearColor(" + 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.info("GLES20.glPolygonOffset(" + 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.info("GLES20.glPolygonOffset(" + 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.info("GLES20.glViewport(" + 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.info("GLES20.glScissor(" + 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(){ + 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.info("GLES20.glGetUniformLocation(" + 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.warning("Uniform [" + uniform.getName() + "] is not declared in shader."); + }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.info("GLES20.glUseProgram(" + 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.warning("no location for uniform [" + uniform.getName() + "]"); + return; + } + + if (loc == -2){ + // get uniform location + updateUniformLocation(shader, uniform); + if (uniform.getLocation() == -1){ + // not declared, ignore + + if (verboseLogging) + logger.warning("not declared uniform: [" + 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, 1, fb); + break; + case Vector2Array: + if (verboseLogging) + logger.info("GLES20.glUniform2fv set Vector2Array." + uniform.getName()); + fb = (FloatBuffer)uniform.getValue(); + GLES20.glUniform2fv(loc, 1, fb); + break; + case Vector3Array: + if (verboseLogging) + logger.info("GLES20.glUniform3fv set Vector3Array." + uniform.getName()); + fb = (FloatBuffer)uniform.getValue(); + GLES20.glUniform3fv(loc, 1, fb); + break; + case Vector4Array: + if (verboseLogging) + logger.info("GLES20.glUniform4fv set Vector4Array." + uniform.getName()); + fb = (FloatBuffer)uniform.getValue(); + GLES20.glUniform4fv(loc, 1, fb); + break; + case Matrix4Array: + if (verboseLogging) + logger.info("GLES20.glUniform4fv set Matrix4Array." + uniform.getName()); + fb = (FloatBuffer)uniform.getValue(); + GLES20.glUniformMatrix4fv(loc, 1, 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 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 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 + ")"); + + 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(); + int length = intBuf1.get(0); + if (length > 3){ + // get infos + ByteBuffer logBuf = BufferUtils.createByteBuffer(length); + if (verboseLogging) + logger.info("GLES20.glGetShaderInfoLog(" + id + ")"); + infoLog = GLES20.glGetShaderInfoLog(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{ + if (infoLog != null){ + logger.log(Level.WARNING, "compile error: " + source.getName() + ", " + infoLog); + }else{ + logger.log(Level.WARNING, "compile error: " + source.getName()); + } + logger.log(Level.WARNING, source.getDefines() + "\n" + source.getSource()); + } + + 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){ + logger.log(Level.WARNING, "shader link failure. \n{0}", infoLog); + }else{ + logger.warning("shader link failure"); + } + } + + 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){ + 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 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); + } + } + + 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 +// 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); + } + } +*/ + } + + 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 (!img.hasMipmaps() && mips){ + // No pregenerated mips available, + // generate from base level if required +// if (!GLContext.getCapabilities().GL_EXT_framebuffer_multisample){ + + if (verboseLogging) + logger.info("GLES20.glTexParameteri(" + target + "GLES11.GL_GENERATE_MIMAP, GLES20.GL_TRUE)"); + + GLES20.glTexParameteri(target, GLES11.GL_GENERATE_MIPMAP, GLES20.GL_TRUE); +// } + }else{ +// glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 ); + if (img.getMipMapSizes() != null){ +// GLES20.glTexParameteri(target, GLES11.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length ); + } + } + + + if (target == GLES20.GL_TEXTURE_CUBE_MAP){ + List 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(gl, img, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, tdc, true, powerOf2); + } + }/*else if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT){ + List 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(gl, img, target, 0, 0, tdc, true, powerOf2); + + if (verboseLogging) + logger.info("GLES20.glTexParameteri(" + target + "GLES11.GL_GENERATE_MIMAP, GLES20.GL_TRUE)"); + + GLES20.glTexParameteri(target, GLES11.GL_GENERATE_MIPMAP, GLES20.GL_TRUE); + } + +// if (GLContext.getCapabilities().GL_EXT_framebuffer_multisample){ +// glGenerateMipmapEXT(target); +// } + + img.clearUpdateNeeded(); + } + + public void setTexture(int unit, Texture tex){ + Image image = tex.getImage(); + if (image.isUpdateNeeded()){ + 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().name()); + 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() + ")" + ); + + GLES20.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + vb.getData()); + + 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.info("GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, " + 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.info("glDrawElements(): " + 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.info("glDrawElements(), indexBuf.capacity (" + indexBuf.getData().capacity() + "), vertCount (" + 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.info("updateVertexArray(" + 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); + } + + IntMap buffers = mesh.getBuffers(); + for (Entry 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); + } + } + } + + + /** + * 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 buffers = mesh.getBuffers(); + for (Entry 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_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 = buffers.get(Type.Index.ordinal()); + } + if (indices != null) + { + drawTriangleList_Array(indices, mesh, count); + } + else + { + if (verboseLogging) + logger.info("GLES20.glDrawArrays(" + 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.info("renderMeshDefault(" + mesh + ", " + lod + ", " + count + ")"); + VertexBuffer indices = null; + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()){ + updateBufferData(interleavedData); + } + + IntMap buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0){ + indices = mesh.getLodLevel(lod); + }else{ + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry 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{ +// throw new UnsupportedOperationException("Cannot render without index buffer"); + if (verboseLogging) + logger.info("GLES20.glDrawArrays(" + convertElementMode(mesh.getMode()) + ", 0, " + 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.info("GLES10.glPointSize(" + mesh.getPointSize() + ")"); + + GLES10.glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()){ + + if (verboseLogging) + logger.info("GLES20.glLineWidth(" + 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); + } + +// } + } + + private void checkGLError() { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + logger.warning("glError " + error); + // throw new RuntimeException("glError " + error); + } + } + + private boolean log(String message) { + logger.info(message); + return true; + } + + /** + * drawTriangleList_Array uses Vertex Array + * @param indexBuf + * @param mesh + * @param count + */ + public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int count) + { + if (verboseLogging) + logger.info("drawTriangleList_Array(Count = " + 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.info("glDrawElements(): " + elementLength + ", " + curOffset); + + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + + curOffset += elementLength * elSize; + } + } + else //if (mesh.getMode() == Mode.Hybrid) + { + if (verboseLogging) + logger.info("glDrawElements(), indexBuf.capacity (" + indexBuf.getData().capacity() + "), vertCount (" + 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.info("setVertexAttrib_Array(" + 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().name()); + int loc = attrib.getLocation(); + if (loc == -1) + { + //throw new IllegalArgumentException("Location is invalid for attrib: [" + vb.getBufferType().name() + "]"); + if (verboseLogging) + logger.warning("attribute is invalid in shader: [" + vb.getBufferType().name() + "]"); + return; + } + else if (loc == -2) + { + String attributeName = "in" + vb.getBufferType().name(); + + if (verboseLogging) + logger.info("GLES20.glGetAttribLocation(" + programId + ", " + attributeName + ")"); + + loc = GLES20.glGetAttribLocation(programId, attributeName); + 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); + } + + } // 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.info("GLES20.glVertexAttribPointer(" + + "location=" + loc + ", " + + "numComponents=" + vb.getNumComponents() + ", " + + "format=" + vb.getFormat() + ", " + + "isNormalized=" + vb.isNormalized() + ", " + + "stride=" + vb.getStride() + ", " + + "data.capacity=" + 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) + { + // TODO Auto-generated method stub + } +} 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 000000000..08ca07051 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/TextureUtil.java @@ -0,0 +1,246 @@ +package com.jme3.renderer.android; + +import android.graphics.Bitmap; +import android.opengl.GLUtils; +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; + default: + throw new UnsupportedOperationException("Unrecognized format: "+fmt); + } + } + + private static void buildMipmap(GL10 gl, 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; + } + } + + private static void uploadTextureBitmap(GL10 gl, 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(gl, bitmap); + }else{ + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); + bitmap.recycle(); + } + } + + public static void uploadTexture( + GL10 gl, + Image img, + int target, + int index, + int border, + boolean tdc, + boolean generateMips, + boolean powerOf2){ + + if (img.getEfficentData() instanceof Bitmap){ + Bitmap bitmap = (Bitmap) img.getEfficentData(); + uploadTextureBitmap(gl, bitmap, generateMips, powerOf2); +// img.setEfficentData(null); + return; + } + + 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 format = -1; + int dataType = -1; + + switch (fmt){ + case Alpha16: + format = gl.GL_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case Alpha8: + format = gl.GL_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case Luminance8: + format = gl.GL_LUMINANCE; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case Luminance8Alpha8: + format = gl.GL_LUMINANCE_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case Luminance16Alpha16: + format = gl.GL_LUMINANCE_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case Luminance16: + format = gl.GL_LUMINANCE; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case RGB565: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_SHORT_5_6_5; + break; + case ARGB4444: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_SHORT_4_4_4_4; + break; + case RGB10: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case RGB16: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case RGB5A1: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_SHORT_5_5_5_1; + break; + case RGB8: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case BGR8: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case RGBA16: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case RGBA8: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + default: + throw new UnsupportedOperationException("Unrecognized format: "+fmt); + } + + if (data != null) + gl.glPixelStorei(gl.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(); + gl.glCompressedTexImage2D(gl.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){ + gl.glCompressedTexImage2D(gl.GL_TEXTURE_2D, + i, + format, + mipWidth, + mipHeight, + 0, + data.remaining(), + data); + }else{ + gl.glTexImage2D(gl.GL_TEXTURE_2D, + i, + format, + mipWidth, + mipHeight, + 0, + format, + dataType, + data); + } + + pos += mipSizes[i]; + } + } + +} diff --git a/engine/src/android/com/jme3/system/JmeSystem.java b/engine/src/android/com/jme3/system/JmeSystem.java new file mode 100644 index 000000000..f7e07d6cb --- /dev/null +++ b/engine/src/android/com/jme3/system/JmeSystem.java @@ -0,0 +1,101 @@ +package com.jme3.system; + +import android.content.res.Resources; +import com.jme3.util.AndroidLogHandler; +import com.jme3.asset.AndroidAssetManager; +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.AudioParam; +import com.jme3.audio.Environment; +import com.jme3.audio.Listener; +import com.jme3.audio.ListenerParam; +//import com.jme3.audio.DummyAudioRenderer; +import com.jme3.system.JmeContext.Type; +import com.jme3.system.android.OGLESContext; +import com.jme3.util.JmeFormatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import java.net.URL; + + + +public class JmeSystem { + + private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); + + private static boolean initialized = false; + private static Resources res; + + public static void initialize(AppSettings settings){ + if (initialized) + return; + + initialized = true; + try { + JmeFormatter formatter = new JmeFormatter(); + + Handler consoleHandler = new AndroidLogHandler(); + consoleHandler.setFormatter(formatter); +// Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]); +// Logger.getLogger("").addHandler(consoleHandler); + +// Logger.getLogger("com.g3d").setLevel(Level.FINEST); + } catch (SecurityException ex){ + logger.log(Level.SEVERE, "Security error in creating log file", ex); + } + logger.info("Running on "+getFullName()); + } + + public static String getFullName(){ + return "jMonkey Engine 3 ALPHA 0.50"; + } + + public static JmeContext newContext(AppSettings settings, Type contextType) { + initialize(settings); + return new OGLESContext(); + } + + public static AudioRenderer newAudioRenderer(AppSettings settings) { + return new AudioRenderer() { + public void setListener(Listener listener) {} + 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 deleteAudioData(AudioData ad) {} + public void initialize() {} + public void update(float tpf) {} + public void cleanup() {} + public void updateListenerParam(Listener listener, ListenerParam parameter) {} + public void updateSourceParam(AudioNode node, AudioParam parameter) {} + }; + } + + public static void setResources(Resources res){ + JmeSystem.res = res; + } + + public static Resources getResources(){ + return res; + } + + public static AssetManager newAssetManager(){ + logger.info("newAssetManager()"); + return new AndroidAssetManager(true); + } + + public static AssetManager newAssetManager(URL url){ + logger.info("newAssetManager(" + url + ")"); + return new AndroidAssetManager(true); + } + + public static boolean showSettingsDialog(AppSettings settings) { + return true; + } + +} 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 000000000..ef0b76898 --- /dev/null +++ b/engine/src/android/com/jme3/system/android/AndroidTimer.java @@ -0,0 +1,94 @@ +/* + * 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; + +/** + * NanoTimer is a System.nanoTime implementation of Timer. + * This is primarily useful for headless applications running on a server. + * + * @author Matthew D. Hicks + */ +public class AndroidTimer extends Timer { + + private static final long TIMER_RESOLUTION = 1000L; + private static final float INVERSE_TIMER_RESOLUTION = 1f/1000L; + + private long startTime; + private long previousTime; + private float tpf; + private float fps; + + public AndroidTimer() { + startTime = System.currentTimeMillis(); + } + + /** + * 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; + } + + 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(); + previousTime = getTime(); + } +} 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 000000000..be01b1a85 --- /dev/null +++ b/engine/src/android/com/jme3/system/android/OGLESContext.java @@ -0,0 +1,286 @@ +/* + * 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.opengl.GLSurfaceView; +import android.view.SurfaceHolder; +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.android.AndroidInput; +//import com.jme3.renderer.android.OGLESRenderer; +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 java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + + +public class OGLESContext implements JmeContext, GLSurfaceView.Renderer { + + private static final Logger logger = Logger.getLogger(OGLESContext.class.getName()); + + protected AtomicBoolean created = new AtomicBoolean(false); + protected AppSettings settings = new AppSettings(true); + + /* < OpenGL ES 2.0 * */ + //protected OGLESRenderer renderer; + /* >= OpenGL ES 2.0 (Android 2.2+) */ + protected OGLESShaderRenderer renderer; + + protected Timer timer; + protected SystemListener listener; + + protected AtomicBoolean needClose = new AtomicBoolean(false); + protected boolean wasActive = false; + protected int frameRate = 0; + protected boolean autoFlush = true; + + protected AndroidInput view; + + public OGLESContext(){ + } + + public Type getType() { + return Type.Display; + } + + public GLSurfaceView createView(Activity activity){ + view = new AndroidInput(activity); + + /* + * Requesting client version from GLSurfaceView which is extended by + * AndroidInput. + * This is required to get OpenGL ES 2.0 + */ + + logger.info("setEGLContextClientVersion(2)"); + view.setEGLContextClientVersion(2); + logger.info("setEGLContextClientVersion(2) ... done."); + + //RGB565, Depth16 + view.setEGLConfigChooser(5, 6, 5, 0, 16, 0); + view.setFocusableInTouchMode(true); + view.setFocusable(true); + view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); +// view.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR); +// | GLSurfaceView.DEBUG_LOG_GL_CALLS); + view.setRenderer(this); + return view; + + } + + protected void applySettings(AppSettings setting){ + } + + protected void initInThread(GL10 gl){ + logger.info("Display created."); + logger.fine("Running on thread: "+Thread.currentThread().getName()); + + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + } + }); + + created.set(true); + + timer = new AndroidTimer(); + + renderer = new OGLESShaderRenderer(gl); + applySettingsToRenderer(renderer, settings); + + renderer.initialize(); + listener.initialize(); + + // OGLESShaderRenderer does not support guiView yet + // forcefully remove all gui nodes + + if (listener instanceof com.jme3.app.SimpleApplication) { + ((com.jme3.app.SimpleApplication) listener).getGuiNode().detachAllChildren(); + } + } + + /** + * De-initialize in the OpenGL thread. + */ + protected void deinitInThread(){ + listener.destroy(); + if (renderer != null) { + renderer.cleanup(); + // do android specific cleaning here + + logger.info("Display destroyed."); + created.set(false); + renderer = null; + timer = null; + } + } + + + 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")); + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + + // XXX This code should be somewhere else + if (renderer != null) + applySettingsToRenderer(renderer, this.settings); + } + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + public AppSettings getSettings() { + return settings; + } + + public com.jme3.renderer.Renderer getRenderer() { + return renderer; + } + + public MouseInput getMouseInput() { + return view; + } + + public KeyInput getKeyInput() { + return view; + } + + public JoyInput getJoyInput() { + return null; + } + + public Timer getTimer() { + return timer; + } + + public void setTitle(String title) { + } + + public boolean isCreated(){ + return created.get(); + } + + public void setAutoFlushFrames(boolean enabled){ + this.autoFlush = enabled; + } + + // renderer:initialize + public void onSurfaceCreated(GL10 gl, EGLConfig cfg) { + logger.info("Using Android"); + initInThread(gl); + } + + // SystemListener:reshape + public void onSurfaceChanged(GL10 gl, int width, int height) { + settings.setResolution(width, height); + listener.reshape(width, height); + } + + // SystemListener:update + public void onDrawFrame(GL10 gl) { + if (needClose.get()){ + deinitInThread(); // ??? + return; + } + +// if (wasActive != Display.isActive()){ +// if (!wasActive){ +// listener.gainFocus(); +// wasActive = true; +// }else{ +// listener.loseFocus(); +// wasActive = false; +// } +// } + + if (!created.get()) + throw new IllegalStateException(); + + listener.update(); + + // swap buffers + + if (frameRate > 0){ +// Display.sync(frameRate); + // synchronzie to framerate + } + + if (autoFlush) + renderer.onFrame(); + } + + /** + * TODO: get these methods to follow the spec + * @param waitFor + */ + public void create(boolean waitFor) { + if (created.get()){ + logger.warning("create() called when display is already created!"); + return; + } + } + + public void create(){ + create(false); + } + + public void restart() { + } + + /** + * TODO: get these methods to follow the spec + * @param waitFor + */ + public void destroy(boolean waitFor) { + needClose.set(true); + } + + public void destroy(){ + destroy(false); + } + +} 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 000000000..e28ad5d41 --- /dev/null +++ b/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java @@ -0,0 +1,95 @@ +package com.jme3.texture.plugins; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +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; + +public class AndroidImageLoader implements AssetLoader { + + public Object load2(AssetInfo info) throws IOException { + ByteBuffer bb = BufferUtils.createByteBuffer(1 * 1 * 2); + bb.put( (byte) 0xff ).put( (byte) 0xff ); + bb.clear(); + return new Image(Format.RGB5A1, 1, 1, bb); + } + + public Object load(AssetInfo info) throws IOException { + InputStream in = null; + Bitmap bitmap = null; + try { + in = info.openStream(); + bitmap = BitmapFactory.decodeStream(in); + if (bitmap == null){ + throw new IOException("Failed to load image: "+info.getKey().getName()); + } + } finally { + if (in != null) + in.close(); + } + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int bytesPerPixel = -1; + Format fmt; + + switch (bitmap.getConfig()){ + case ALPHA_8: + bytesPerPixel = 1; + fmt = Format.Alpha8; + break; + case ARGB_4444: + bytesPerPixel = 2; + fmt = Format.ARGB4444; + break; + case ARGB_8888: + bytesPerPixel = 4; + fmt = Format.RGBA8; + break; + case RGB_565: + bytesPerPixel = 2; + fmt = Format.RGB565; + break; + default: + return null; + } + +// if (width > 128 || height > 128){ +// if (width > height){ +// float aspect = (float) height / width; +// width = 128; +// height = (int) (128 * aspect); +// +// }else{ +// float aspect = (float) width / height; +// width = (int) (128 * aspect); +// height = 128; +// } +// bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); +// } + +// if ( ((TextureKey)info.getKey()).isFlipY() ){ +// Bitmap newBitmap = null; +// Matrix flipMat = new Matrix(); +// flipMat.preScale(1.0f, -1.0f); +// newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), flipMat, false); +// bitmap.recycle(); +// bitmap = newBitmap; +// +// if (bitmap == null){ +// throw new IOException("Failed to load image2: "+info.getKey().getName()); +// } +// } + + Image image = new Image(fmt, width, height, null); + image.setEfficentData(bitmap); + 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 000000000..8fb21c22c --- /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/jme3test/android/AboutActivity.java b/engine/src/android/jme3test/android/AboutActivity.java new file mode 100644 index 000000000..44797d1f2 --- /dev/null +++ b/engine/src/android/jme3test/android/AboutActivity.java @@ -0,0 +1,72 @@ +package jme3test.android; + + +import java.util.ArrayList; +import java.util.List; + +import android.app.Activity; +import android.os.Bundle; + +import android.content.Intent; + +import android.view.View; +import android.view.MenuInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; + +import android.widget.TextView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.CheckBox; +import android.widget.TableLayout; +import android.widget.LinearLayout; +import android.widget.TableRow; + +import android.hardware.SensorManager; +//import android.hardware.SensorListener; + +import jme3test.android.AndroidActivity; + +import java.net.URI; + + +public class AboutActivity extends Activity { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(AboutActivity.class.getName()); + + + @Override + public void onCreate(Bundle savedInstanceState) { + logger.info("onCreate(" + savedInstanceState + ")"); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.about); + } + + @Override + public void onDestroy() { + logger.info("onDestroy()"); + super.onDestroy(); + } + + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + +} + diff --git a/engine/src/android/jme3test/android/AndroidActivity.java b/engine/src/android/jme3test/android/AndroidActivity.java new file mode 100644 index 000000000..350d3c202 --- /dev/null +++ b/engine/src/android/jme3test/android/AndroidActivity.java @@ -0,0 +1,158 @@ + +/* + * + * Android Activity for OpenGL ES2 based tests + * requires Android 2.2+ + * + * created: Mon Nov 8 00:08:07 EST 2010 + */ + +package jme3test.android; + +import android.app.Activity; +import android.content.res.Resources; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; +import com.jme3.R; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import com.jme3.system.android.OGLESContext; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; + + +public class AndroidActivity extends Activity { + + private final static java.util.logging.Logger logger = java.util.logging.Logger.getLogger(AndroidActivity.class.getName()); + + + private OGLESContext ctx; + private GLSurfaceView view; + + private boolean useVA = false; + private boolean verboseLogging = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + JmeSystem.setResources(getResources()); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + AppSettings settings = new AppSettings(true); + + String testClassName = getIntent().getStringExtra(AndroidActivity.class.getName() + ".TEST_CLASS_NAME"); + + logger.info("test class name: [" + testClassName + "]"); + + String appClass = (testClassName != null? testClassName: "jme3test.android.SimpleTexturedTest"); + + useVA = getIntent().getBooleanExtra(AndroidActivity.class.getName() + ".USE_VA", false); + + logger.info("USE_VA -> [" + useVA + "]"); + + settings.putBoolean("USE_VA", useVA); + + verboseLogging = getIntent().getBooleanExtra(AndroidActivity.class.getName() + ".VERBOSE_LOGGING", false); + + settings.putBoolean("VERBOSE_LOGGING", verboseLogging); + + Application app = null; + + try { + Class clazz = (Class) Class.forName( + appClass + ); + + app = clazz.newInstance(); +/* + app = (Application) java.lang.reflect.Proxy.newProxyInstance( + this.getClass().getClassLoader(), + new Class[] {Class.forName(appClass)}, + + new java.lang.reflect.InvocationHandler() { + public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable { + if ( + method.getName().equals("loadFPSText") || + method.getName().equals("loadStatsView") + ) { + logger.info("ignoring method: [" + method + "]"); + return null; + } + + return method.invoke(proxy, args); + } + } + ); +*/ + + + if (app instanceof SimpleApplication) { + ((SimpleApplication) app).setShowSettings(false); + } + + logger.info("setting settings ..."); + app.setSettings(settings); + logger.info("setting settings ... done."); + + logger.info("starting app ..."); + app.start(); + logger.info("starting app ... done."); + + if (app instanceof SimpleApplication) + ((SimpleApplication) app).getGuiNode().detachAllChildren(); + + logger.info("creating context ..."); + ctx = (OGLESContext) app.getContext(); + logger.info("creating context ... done."); + + ctx.setSettings(settings); + + logger.info("creating view ..."); + view = ctx.createView(this); + logger.info("creating view ... done."); + + logger.info("setting content view ..."); + setContentView(view); + logger.info("setting content done ..."); + + } catch (Throwable exception) { + logger.warning("exception: " + exception); + exception.printStackTrace(System.err); + } + } + + @Override + protected void onResume() { + logger.info("onResume ..."); + super.onResume(); + logger.info("view.onResume ..."); + + view.onResume(); + + logger.info("view.onResume ... done."); + logger.info("onResume ... done."); + } + + @Override + protected void onPause() { + super.onPause(); + view.onPause(); + } + +// @Override +// protected void onDestroy(){ +// super.onDestroy(); + +// Debug.stopMethodTracing(); +// } + +} + diff --git a/engine/src/android/jme3test/android/SimpleTexturedTest.java b/engine/src/android/jme3test/android/SimpleTexturedTest.java new file mode 100644 index 000000000..635d8e188 --- /dev/null +++ b/engine/src/android/jme3test/android/SimpleTexturedTest.java @@ -0,0 +1,135 @@ + +/* + * Android 2.2+ SimpleTextured test. + * + * created: Mon Nov 8 00:08:22 EST 2010 + */ + +package jme3test.android; + + +import java.util.List; +import java.util.ArrayList; + +import com.jme3.app.SimpleApplication; + +import com.jme3.asset.TextureKey; + +import com.jme3.material.Material; + +import com.jme3.math.Transform; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Box; + +import com.jme3.texture.Texture; + +import com.jme3.light.PointLight; + +import com.jme3.util.TangentBinormalGenerator; + +import jme3tools.converters.model.ModelConverter; + + +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() { + + /* + * GUI rendering is broken on Android right now and prevents the main view from rendering. + * Detaching all children lets the main view to be rendered. + */ + + guiNode.detachAllChildren(); + + Mesh shape = null; + + if (spheres) { + shape = new Sphere(16, 16, .5f); + } else { + shape = new Box(Vector3f.ZERO, 0.3f, 0.3f, 0.3f); + } + + // ModelConverter.optimize(geom); + + Texture texture = assetManager.loadTexture(new TextureKey("icons/textured.png")); + + Material material = 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); + } else { + material = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + material.setTexture("ColorMap", texture); + } + } else { + material = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md"); + material.setColor("Color", ColorRGBA.Red); + } + + TangentBinormalGenerator.generate(shape); + + for (int y = -1; y < 2; y++) { + for (int x = -1; x < 2; x++){ + // int x = 0; + // int y = 0; + Geometry geomClone = new Geometry("geometry-" + y + "-" + x, shape); + 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) { + + 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/Test.java b/engine/src/android/jme3test/android/Test.java new file mode 100644 index 000000000..f2c529682 --- /dev/null +++ b/engine/src/android/jme3test/android/Test.java @@ -0,0 +1,41 @@ +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.math.Transform; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Texture; +import jme3tools.converters.model.ModelConverter; + +public class Test extends SimpleApplication { + + @Override + public void simpleInitApp() { + Sphere s = new Sphere(8, 8, .5f); + Geometry geom = new Geometry("sphere", s); + // ModelConverter.optimize(geom); + + Material mat = new Material(assetManager, "plain_texture.j3md"); + Texture tex = assetManager.loadTexture(new TextureKey("monkey.j3i")); + mat.setTexture("ColorMap", tex); +// geom.setMaterial(mat); + + for (int y = -1; y < 2; y++){ + for (int x = -1; x < 2; x++){ + Geometry geomClone = new Geometry("geom", s); + geomClone.setMaterial(mat); + geomClone.setLocalTranslation(x, y, 0); + + Transform t = geom.getLocalTransform().clone(); + Transform t2 = geomClone.getLocalTransform().clone(); + t.combineWithParent(t2); + geomClone.setLocalTransform(t); + + rootNode.attachChild(geomClone); + } + } + } + +} diff --git a/engine/src/android/jme3test/android/TestSceneLoading.java b/engine/src/android/jme3test/android/TestSceneLoading.java new file mode 100644 index 000000000..f97aaf091 --- /dev/null +++ b/engine/src/android/jme3test/android/TestSceneLoading.java @@ -0,0 +1,37 @@ +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; + + +public class TestSceneLoading extends SimpleApplication { + + private void setState(Spatial s){ + s.setCullHint(CullHint.Never); + if (s instanceof Node){ + Node n = (Node) s; + for (int i = 0; i < n.getQuantity(); i++){ + Spatial s2 = n.getChild(i); + setState(s2); + } + } + } + + public void simpleInitApp() { + /* XXX: does not compile */ + +/* Spatial scene = inputManager.loadModel("FINAL_LEVEL2.j3o"); +// setState(scene); + rootNode.attachChild(scene); + + cam.setLocation(new Vector3f(-18.059685f, 34.64228f, 4.5048084f)); + cam.setRotation(new Quaternion(0.22396432f, 0.5235024f, -0.1448922f, 0.8091919f)); + cam.update(); +*/ + } + +} diff --git a/engine/src/android/jme3test/android/TestsActivity.java b/engine/src/android/jme3test/android/TestsActivity.java new file mode 100644 index 000000000..dcf34a43e --- /dev/null +++ b/engine/src/android/jme3test/android/TestsActivity.java @@ -0,0 +1,178 @@ +package jme3test.android; + + +import java.util.ArrayList; +import java.util.List; + +import android.app.Activity; +import android.os.Bundle; + +import android.content.Intent; + +import android.view.View; +import android.view.MenuInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; + +import android.widget.TextView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.CheckBox; +import android.widget.TableLayout; +import android.widget.LinearLayout; +import android.widget.TableRow; + +import android.hardware.SensorManager; +//import android.hardware.SensorListener; + +import jme3test.android.AndroidActivity; + +import java.net.URI; + + +public class TestsActivity extends Activity { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(TestsActivity.class.getName()); + + + public static class Test { + + private String name = null; + private String className = null; + + public Test(String name, String className) { + this.name = name; + this.className = className; + } + + public String getName() { + return name; + } + + public String getClassName() { + return className; + } + } + + private final static Test[] tests = { + new Test("SimpleTextured", "jme3test.android.SimpleTexturedTest"), + new Test("light.TestLightRadius", "jme3test.light.TestLightRadius"), + new Test("bullet.TestSimplePhysics", "jme3test.bullet.TestSimplePhysics"), + new Test("helloworld.HelloJME3", "jme3test.helloworld.HelloJME3"), + new Test("helloworld.HelloLoop", "jme3test.helloworld.HelloLoop"), + new Test("helloworld.HelloNode", "jme3test.helloworld.HelloNode"), + new Test("helloworld.HelloEffects", "jme3test.helloworld.HelloEffects"), + new Test("helloworld.HelloTerrain", "jme3test.helloworld.HelloTerrain") + }; + + private CheckBox useVA = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + logger.info("onCreate(" + savedInstanceState + ")"); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.tests); + + try { + + useVA = (CheckBox) findViewById(R.id.useVA); + + LinearLayout buttonsContainer = (LinearLayout) findViewById(R.id.buttonsContainer); + + + for (Test test: tests) { + final Button button = new Button(this); + final String finalName = test.getName(); + final String finalClassName = test.getClassName(); + + button.setText(test.getName()); +// button.setTextSize(10.0f); +// button.setTextColor(Color.rgb(100, 200, 200)); + buttonsContainer.addView(button); + + button.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(view.getContext(), AndroidActivity.class); + intent.putExtra(AndroidActivity.class.getName() + ".TEST_CLASS_NAME", finalClassName); + intent.putExtra(AndroidActivity.class.getName() + ".USE_VA", useVA.isChecked()); + startActivityForResult(intent, 0); + } + } + ); + } + } catch (Exception exception) { + logger.warning("exception: " + exception); + exception.printStackTrace(System.err); + } + } + + @Override + public void onDestroy() { + logger.info("onDestroy()"); + super.onDestroy(); + } + + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.options, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.about_button: + about(); + return true; + case R.id.quit_button: + quit(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void quit() { + finish(); + } + + private void about() { + // Intent intent = new Intent(getView().getContext(), AboutActivity.class); + try { + Intent intent = new Intent(); + intent.setClassName( + "jme3test.android", + "jme3test.android.AboutActivity" + ); + startActivity(intent); + } catch (Exception exception) { + logger.warning("exception: " + exception); + exception.printStackTrace(System.err); + } + } + + +} + diff --git a/engine/src/android/jme3tools/android/Fixed.java b/engine/src/android/jme3tools/android/Fixed.java new file mode 100644 index 000000000..db7090400 --- /dev/null +++ b/engine/src/android/jme3tools/android/Fixed.java @@ -0,0 +1,437 @@ +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 FIXED_POINT + * , which would also require SIN_PRECALC and + * COS_PRECALC updating). + * + * http://blog.numfum.com/2007/09/java-fixed-point-maths.html + * + * @version 1.0 + * @author CW + */ +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)) + *

+ * 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 TABLE_SHIFT). + */ + 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 TABLE_SHIFT). + */ + 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 java.utll.Random 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. + *

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

+ * 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 + * Math.round() 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 Math.floor() 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 Math.ceil() 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 n (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 000000000..d95af3722 --- /dev/null +++ b/engine/src/android/res/layout/about.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/engine/src/android/res/layout/tests.xml b/engine/src/android/res/layout/tests.xml new file mode 100644 index 000000000..afd405b87 --- /dev/null +++ b/engine/src/android/res/layout/tests.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/engine/src/android/res/menu/options.xml b/engine/src/android/res/menu/options.xml new file mode 100644 index 000000000..7f7d527fa --- /dev/null +++ b/engine/src/android/res/menu/options.xml @@ -0,0 +1,14 @@ + +

+ + + + + + diff --git a/engine/src/android/res/values/strings.xml b/engine/src/android/res/values/strings.xml new file mode 100644 index 000000000..2705a3ef6 --- /dev/null +++ b/engine/src/android/res/values/strings.xml @@ -0,0 +1,6 @@ + + + JMEAndroidTest + About + Quit + 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 000000000..ee22b51dc --- /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(void) +{ 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 000000000..ddb262e91 --- /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 000000000..b3adfd425 --- /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 <= t <= 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 000000000..1e397fa5b --- /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 000000000..8b1f1213e --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur15.frag @@ -0,0 +1,48 @@ +#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; + +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 <= t <= 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/VGaussianBlur.frag b/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.frag new file mode 100644 index 000000000..3e20fe56d --- /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 000000000..163f9f383 --- /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 000000000..caa666c1b --- /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 000000000..a8dab82f4 --- /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 000000000..0591c5e89 --- /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 000000000..68f08e9a6 --- /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 000000000..0c4c6c889 --- /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 000000000..f22103072 --- /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 000000000..24fbd04ae --- /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 000000000..9fc7ebb8b --- /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 000000000..c386a6165 --- /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 000000000..0743cc1a9 --- /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 000000000..397062455 --- /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 000000000..f4ad19963 --- /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 000000000..8b613f566 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Light/Glow.frag @@ -0,0 +1,23 @@ +varying vec2 texCoord; + +#ifdef HAS_GLOWMAP + uniform sampler2D m_GlowMap; +#endif + +#ifdef HAS_GLOWCOLOR + uniform vec4 m_GlowColor; +#endif + + +void main(){ + + #ifdef HAS_GLOWMAP + gl_FragColor = texture2D(m_GlowMap, texCoord); + #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 000000000..8d5c785e0 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.frag @@ -0,0 +1,207 @@ +#import "Common/ShaderLib/Optics.glsllib" +#define ATTENUATION +//#define HQ_ATTENUATION + +varying vec2 texCoord; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +#ifndef VERTEX_LIGHTING + varying vec3 vPosition; + varying vec3 vViewDir; + varying vec4 vLightDir; +#endif + +#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; +#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; +varying vec3 lightVec; +#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){ + #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 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); + + #ifdef HQ_ATTENUATION + float att = clamp(1.0 - g_LightPosition.w * length(lightVec), 0.0, 1.0); + #else + float att = vLightDir.w; + #endif + + return vec2(diffuseFactor, specularFactor) * vec2(att); +} +#endif + +void main(){ + vec2 newTexCoord; + + #if defined(PARALLAXMAP) || defined(NORMALMAP_PARALLAX) + float h; + #ifdef PARALLAXMAP + h = texture2D(m_ParallaxMap, texCoord).r; + #else + h = texture2D(m_NormalMap, texCoord).a; + #endif + float heightScale = 0.05; + float heightBias = heightScale * -0.5; + vec3 normView = normalize(vViewDir); + h = (h * heightScale + heightBias) * normView.z; + newTexCoord = texCoord + (h * -normView.xy); + #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; + } + + // *********************** + // 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 VERTEX_LIGHTING + vec2 light = vec2(AmbientSum.a, SpecularSum.a); + #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 = AmbientSum * diffuseColor + + DiffuseSum * diffuseColor * light.x + + SpecularSum * specularColor * light.y; + #else + vec4 lightDir = vLightDir; + lightDir.xyz = normalize(lightDir.xyz); + + vec2 light = computeLighting(vPosition, normal, vViewDir.xyz, lightDir.xyz); + #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 + + #ifdef USE_REFLECTION + vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz); + + // Interpolate light specularity toward reflection color + // Multiply result by specular map + specularColor = mix(SpecularSum * light.y, refColor, refVec.w) * specularColor; + + SpecularSum = vec4(1.0); + light.y = 1.0; + #endif + + gl_FragColor = AmbientSum * diffuseColor + + DiffuseSum * diffuseColor * light.x + + SpecularSum * specularColor * 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 000000000..7114be00b --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md @@ -0,0 +1,219 @@ +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 + + // Diffuse color + Color Diffuse : Color + + // Specular color + Color Specular + + // Specular power/shininess + Float Shininess + + // Diffuse map + Texture2D DiffuseMap + + // Normal map + Texture2D NormalMap + + // Specular/gloss map + Texture2D SpecularMap + + // Parallax/height map + Texture2D ParallaxMap + + // 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 + + // 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 + ALPHAMAP : AlphaMap + COLORRAMP : ColorRamp + + 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 000000000..7de7713e4 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.vert @@ -0,0 +1,185 @@ +#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; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +attribute vec3 inPosition; +attribute vec2 inTexCoord; +attribute vec3 inNormal; + +#ifdef HQ_ATTENUATION + varying vec3 lightVec; +#endif + +#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; +#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); + #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 +} + +#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){ + #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 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); + } +#endif + +void main(){ + vec4 pos = vec4(inPosition, 1.0); + gl_Position = g_WorldViewProjectionMatrix * pos; + texCoord = inTexCoord; + + 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, g_LightColor.w)); + 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; + 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 + + lightColor.w = 1.0; + #ifdef MATERIAL_COLORS + AmbientSum = m_Ambient * g_AmbientLightColor; + DiffuseSum = m_Diffuse * lightColor; + SpecularSum = m_Specular * lightColor; + #else + AmbientSum = vec4(0.2, 0.2, 0.2, 1.0) * g_AmbientLightColor; // Default: ambient color is dark gray + DiffuseSum = lightColor; + SpecularSum = lightColor; + #endif + + #ifdef VERTEX_COLOR + AmbientSum *= inColor; + DiffuseSum *= inColor; + #endif + + #ifdef VERTEX_LIGHTING + vec2 light = computeLighting(wvPosition, wvNormal, viewDir, wvLightPos); + + AmbientSum.a = light.x; + SpecularSum.a = light.y; + #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 000000000..272f100be --- /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 000000000..dde8ea87d --- /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 000000000..572d84191 --- /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 000000000..3c378e6e6 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/Particle.frag @@ -0,0 +1,26 @@ +// TODO: Fix this so normal particles don't need it. +// Only needed for certain GPUs. +//#version 120 + +#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 000000000..25283b27d --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md @@ -0,0 +1,65 @@ +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 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 + } + } +} \ 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 000000000..9c2733615 --- /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 000000000..93e488230 --- /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 000000000..db480b75f --- /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 000000000..3813043b0 --- /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 000000000..395f3d181 --- /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 000000000..c18559c70 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.j3md @@ -0,0 +1,30 @@ +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 000000000..572d84191 --- /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 000000000..1c4dbbe96 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/Sky.frag @@ -0,0 +1,16 @@ +#import "Common/ShaderLib/Optics.glsllib" + +uniform ENVMAP m_Texture; + +varying vec3 direction; + +void main() { + //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + + //gl_FragDepth = 1.0; + vec3 dir = normalize(direction); + gl_FragColor = Optics_GetEnvColor(m_Texture, direction); + //gl_FragColor = vec4(textureCube(m_Texture, dir).xyz, 1.0); + //gl_FragColor = vec4((dir * vec3(0.5)) + vec3(0.5), 1.0); +} + 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 000000000..3a9c05ee0 --- /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 { + NormalMatrix + ViewMatrix + ProjectionMatrix + } + + 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 000000000..63c58a453 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/Sky.vert @@ -0,0 +1,24 @@ +uniform mat4 g_ViewMatrix; +uniform mat4 g_ProjectionMatrix; +uniform mat3 g_NormalMatrix; + +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; + + direction = normalize(inNormal * m_NormalScale); +} 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 000000000..50c8b9d12 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/SolidColor.j3md @@ -0,0 +1,42 @@ +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 000000000..1f3811ad4 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.frag @@ -0,0 +1,50 @@ +uniform vec4 m_Color; + +#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPERATE_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 SEPERATE_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 SEPERATE_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 000000000..c05f1aa2d --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.j3md @@ -0,0 +1,69 @@ +MaterialDef Unshaded { + + MaterialParameters { + Texture2D ColorMap + Texture2D LightMap + Color Color : Color + Boolean VertexColor + Boolean SeperateTexCoord + + // 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 { + SEPERATE_TEXCOORD : SeperateTexCoord + 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 + } + } + + 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 000000000..39dd97300 --- /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(SEPERATE_TEXCOORD)) + #define NEED_TEXCOORD1 +#endif + +#ifdef NEED_TEXCOORD1 + attribute vec2 inTexCoord; + varying vec2 texCoord1; +#endif + +#ifdef SEPERATE_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 SEPERATE_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 000000000..09b3e7bb7 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/VertexColor.j3md @@ -0,0 +1,16 @@ +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 000000000..35debff16 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/WireColor.j3md @@ -0,0 +1,36 @@ +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/Post/BloomExtract.j3md b/engine/src/core-data/Common/MatDefs/Post/BloomExtract.j3md new file mode 100644 index 000000000..76614ccc7 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/BloomFinal.j3md b/engine/src/core-data/Common/MatDefs/Post/BloomFinal.j3md new file mode 100644 index 000000000..68f8c287d --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/CartoonEdge.frag b/engine/src/core-data/Common/MatDefs/Post/CartoonEdge.frag new file mode 100644 index 000000000..7ee7f789f --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/CartoonEdge.j3md b/engine/src/core-data/Common/MatDefs/Post/CartoonEdge.j3md new file mode 100644 index 000000000..687193e8e --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/CartoonEdge15.frag b/engine/src/core-data/Common/MatDefs/Post/CartoonEdge15.frag new file mode 100644 index 000000000..3c3921a98 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/DepthOfField.frag b/engine/src/core-data/Common/MatDefs/Post/DepthOfField.frag new file mode 100644 index 000000000..658da546f --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/DepthOfField.j3md b/engine/src/core-data/Common/MatDefs/Post/DepthOfField.j3md new file mode 100644 index 000000000..db30a0975 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/Fade.frag b/engine/src/core-data/Common/MatDefs/Post/Fade.frag new file mode 100644 index 000000000..b616239d0 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/Fade.j3md b/engine/src/core-data/Common/MatDefs/Post/Fade.j3md new file mode 100644 index 000000000..e93b276b9 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/Fade15.frag b/engine/src/core-data/Common/MatDefs/Post/Fade15.frag new file mode 100644 index 000000000..c99de34ad --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/Fog.frag b/engine/src/core-data/Common/MatDefs/Post/Fog.frag new file mode 100644 index 000000000..7321b1516 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/Fog.j3md b/engine/src/core-data/Common/MatDefs/Post/Fog.j3md new file mode 100644 index 000000000..e5d6c8db7 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/Fog15.frag b/engine/src/core-data/Common/MatDefs/Post/Fog15.frag new file mode 100644 index 000000000..65a340723 --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Post/LightScattering.frag b/engine/src/core-data/Common/MatDefs/Post/LightScattering.frag new file mode 100644 index 000000000..683bbeaa7 --- /dev/null +++ b/engine/src/core-data/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 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_texture_gather + vec4 coord = vec4(projCoord.xyz / projCoord.w,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 000000000..5d957e908 --- /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 000000000..070949f42 --- /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 000000000..fdd3a2511 --- /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/MatDefs/Water/SimpleWater.j3md b/engine/src/core-data/Common/MatDefs/Water/SimpleWater.j3md new file mode 100644 index 000000000..12883242f --- /dev/null +++ b/engine/src/core-data/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-data/Common/MatDefs/Water/Textures/dudv_map.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/dudv_map.jpg new file mode 100644 index 000000000..a2734bbdf Binary files /dev/null and b/engine/src/core-data/Common/MatDefs/Water/Textures/dudv_map.jpg differ diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/foam.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/foam.jpg new file mode 100644 index 000000000..fc17ac291 Binary files /dev/null and b/engine/src/core-data/Common/MatDefs/Water/Textures/foam.jpg differ diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/foam2.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/foam2.jpg new file mode 100644 index 000000000..23a4d0e21 Binary files /dev/null and b/engine/src/core-data/Common/MatDefs/Water/Textures/foam2.jpg differ diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/foam3.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/foam3.jpg new file mode 100644 index 000000000..f8b5e0d0e Binary files /dev/null and b/engine/src/core-data/Common/MatDefs/Water/Textures/foam3.jpg differ diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/gradient_map.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/gradient_map.jpg new file mode 100644 index 000000000..d114d5f97 Binary files /dev/null and b/engine/src/core-data/Common/MatDefs/Water/Textures/gradient_map.jpg differ diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/heightmap.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/heightmap.jpg new file mode 100644 index 000000000..cfb58466c Binary files /dev/null and b/engine/src/core-data/Common/MatDefs/Water/Textures/heightmap.jpg differ diff --git a/engine/src/core-data/Common/MatDefs/Water/Water.frag b/engine/src/core-data/Common/MatDefs/Water/Water.frag new file mode 100644 index 000000000..35eb9f00c --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Water/Water.frag @@ -0,0 +1,297 @@ +// 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_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; + +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); +} + +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 leave out complex computations + if(level >= m_CameraPosition.y){ + gl_FragColor = vec4(color2, 1.0); + 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 +/* float coef=1.0; + if(position.y level){ + color = color2; + } + + gl_FragColor = vec4(color,0.0); + +} \ No newline at end of file diff --git a/engine/src/core-data/Common/MatDefs/Water/Water.j3md b/engine/src/core-data/Common/MatDefs/Water/Water.j3md new file mode 100644 index 000000000..b1bda5481 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Water/Water.j3md @@ -0,0 +1,77 @@ +MaterialDef Advanced Water { + + MaterialParameters { + Int NumSamples + Int NumSamplesDepth + Texture2D FoamMap + 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 + + Boolean UseRipples + Boolean UseHQShoreline + Boolean UseSpecular + Boolean UseFoam + 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 + } + } + + 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_REFRACTION : UseRefraction + } + } + + Technique FixedFunc { + } +} \ No newline at end of file diff --git a/engine/src/core-data/Common/MatDefs/Water/Water15.frag b/engine/src/core-data/Common/MatDefs/Water/Water15.frag new file mode 100644 index 000000000..ee3d74487 --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Water/Water15.frag @@ -0,0 +1,307 @@ +#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_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 bool m_UseRipples, + m_UseHQShoreline, + m_UseSpecular, + m_UseFoam, + m_UseRefraction; + +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); +} + +// NOTE: This will be called even for single-sampling +vec4 main_multiSample(int 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; + + // If we are underwater let's leave out complex computations + if(level >= m_CameraPosition.y){ + return vec4(color2, 1.0); + } + + 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 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 = vec2(0.0); + int samples = 1; + if (m_UseHQShoreline){ + samples = 10; + } + + 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)).r; + float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1, 0)).r; + float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0, -1)).r; + float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0, 1)).r; + + vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude)); + vec3 normal = vec3(0.0); + + if (m_UseRipples){ + 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; + } + + vec3 refraction = color2; + if (m_UseRefraction){ + // 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 + } + + 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); + if (m_UseFoam){ + 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; + } + + vec3 specular = vec3(0.0); + if (m_UseSpecular){ + 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); + } + + 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); + } + gl_FragColor = color / m_NumSamples; + #else + outFragColor = main_multiSample(0); + #endif +} \ No newline at end of file diff --git a/engine/src/core-data/Common/MatDefs/Water/simple_water.frag b/engine/src/core-data/Common/MatDefs/Water/simple_water.frag new file mode 100644 index 000000000..fb1f3e21d --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Water/simple_water.frag @@ -0,0 +1,125 @@ +/* +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) ); + vec4 specular; + 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-data/Common/MatDefs/Water/simple_water.vert b/engine/src/core-data/Common/MatDefs/Water/simple_water.vert new file mode 100644 index 000000000..e6052d8d5 --- /dev/null +++ b/engine/src/core-data/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-data/Common/Materials/RedColor.j3m b/engine/src/core-data/Common/Materials/RedColor.j3m new file mode 100644 index 000000000..c7c8e77e6 --- /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 000000000..ae7209204 --- /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 000000000..1a5d78e95 --- /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 000000000..6b9149bcc --- /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 000000000..8dce15bb4 --- /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 000000000..0a28362cc --- /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 000000000..5db1423ef --- /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 000000000..4d1b40436 --- /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 000000000..6f4cc9074 --- /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 000000000..e0aa75377 --- /dev/null +++ b/engine/src/core-data/Common/ShaderLib/MultiSample.glsllib @@ -0,0 +1,49 @@ +#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 + +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= 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 Shadow_DoDither_2x2(tex, projCoord) + Shadow_BorderCheck(projCoord.xy); +} + + 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 000000000..f0641b636 --- /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 000000000..044fee399 --- /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 000000000..308c13dd7 --- /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 000000000..829f51b47 --- /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 000000000..5dbb5b9e1 --- /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 000000000..15ea60cf7 --- /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 000000000..821f92a6a Binary files /dev/null and b/engine/src/core-data/Interface/Fonts/Console.png 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 000000000..97230e855 --- /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 000000000..3c2dee217 Binary files /dev/null and b/engine/src/core-data/Interface/Fonts/Default.png differ 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 000000000..20da0e735 --- /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/BinaryClassLoader.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryClassLoader.java new file mode 100644 index 000000000..7adf0be6d --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryClassLoader.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.export.binary; + +import java.io.IOException; +import java.util.logging.Logger; + +import com.jme3.export.InputCapsule; +import com.jme3.export.Savable; + +/** + * This class is mis-named and is located in an inappropriate package: + * It is not binary-specific (it is in fact used for XML format too), and it + * is not a java.lang.ClassLoader, which is what "class loader" is for Java + * developers. + * + * @author mpowell + */ +public class BinaryClassLoader { + + /** + * 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, InputCapsule inputCapsule) throws InstantiationException, + IllegalAccessException, ClassNotFoundException, IOException { + + try { + return (Savable)Class.forName(className).newInstance(); + } + catch (InstantiationException e) { + Logger.getLogger(BinaryClassLoader.class.getName()).severe( + "Could not access constructor of class '" + className + "'! \n" + + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup."); + throw e; + } + catch (IllegalAccessException e) { + Logger.getLogger(BinaryClassLoader.class.getName()).severe( + e.getMessage() + " \n" + + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup."); + throw e; + } + } + +} 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 000000000..4151496f0 --- /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 nameFields; + HashMap aliasFields; + + byte[] alias; + String className; + +} 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 000000000..c34672f26 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.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.export.binary; + +import com.jme3.export.JmeExporter; +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +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.) + *

+ * 1. "number of classes" - four bytes - int value representing the number of + * entries in the class lookup table. + *

+ *

+ * CLASS TABLE: There will be X blocks each consisting of numbers 2 thru 9, + * where X = the number read in 1. + *

+ *

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

+ *

+ * 3. "full class name size" - four bytes - int value representing number of + * bytes to read in for next field. + *

+ *

+ * 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 "com.jme.math.Vector3f" + *

+ *

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

+ *

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

+ *

+ * 7. "field type" - 1 byte - a value representing the type of data a field + * contains. This value is taken from the static fields of + * com.jme.util.export.binary.BinaryClassField. + *

+ *

+ * 8. "field name size" - 4 bytes - int value representing the size of the next + * field. + *

+ *

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

+ *

+ * 10. "number of unique objects" - four bytes - int value representing the + * number of data entries in this file. + *

+ *

+ * DATA LOOKUP TABLE: There will be X blocks each consisting of numbers 11 and + * 12, where X = the number read in 10. + *

+ *

+ * 11. "data id" - four bytes - int value identifying a single unique object + * that was saved in this data file. + *

+ *

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

+ *

+ * 13. "future use" - four bytes - hardcoded int value 1. + *

+ *

+ * 14. "root id" - four bytes - int value identifying the top level object. + *

+ *

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

+ * 15. "class alias" - see 2. + *

+ *

+ * 16. "data length" - four bytes - int value representing the length in bytes + * of data stored in fields 17 and 18 for this object. + *

+ *

+ * FIELD ENTRY: There will be X blocks each consisting of numbers 18 and 19 + *

+ *

+ * 17. "field alias" - see 6. + *

+ *

+ * 18. "field data" - 1...X bytes representing the field data. The data length + * is dependent on the field type and contents. + *

+ * + * @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 contentTable + = new IdentityHashMap(); + + protected HashMap locationTable + = new HashMap(); + + // key - class name, value = bco + private HashMap classes + = new HashMap(); + + private ArrayList contentKeys = new ArrayList(); + + 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(); + + int id = processBinarySavable(object); + + // write out tag table + int ttbytes = 0; + int classNum = classes.keySet().size(); + int aliasWidth = ((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, + aliasWidth); + os.write(aliasBytes); + ttbytes += aliasWidth; + + // write classname size & classname + byte[] classBytes = key.getBytes(); + os.write(ByteUtils.convertToBytes(classBytes.length)); + os.write(classBytes); + ttbytes += 4 + classBytes.length; + + 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); + ttbytes += 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> alreadySaved = new HashMap>( + 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 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(); + alreadySaved.put(savableName + getChunk(pair), bucket); + } + bucket.add(pair); + byte[] aliasBytes = fixClassAlias(classes.get(savableName).alias, aliasWidth); + out.write(aliasBytes); + location += aliasWidth; + 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 locNum = locationTable.keySet().size(); + os.write(ByteUtils.convertToBytes(locNum)); + int locbytes = 0; + for (Integer key : locationTable.keySet()) { + os.write(ByteUtils.convertToBytes(key)); + os.write(ByteUtils.convertToBytes(locationTable.get(key))); + locbytes += 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.info("classes: " + classNum); + logger.info("class table: " + ttbytes + " bytes"); + logger.info("objects: " + locNum); + logger.info("location table: " + locbytes + " bytes"); + logger.info("data: " + location + " bytes"); + } + + 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 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(); + } + + public int processBinarySavable(Savable object) throws IOException { + if (object == null) { + return -1; + } + BinaryClassObject bco = classes.get(object.getClass().getName()); + // is this class been looked at before? in tagTable? + if (bco == null) { + bco = new BinaryClassObject(); + bco.alias = generateTag(); + bco.nameFields = new HashMap(); + classes.put(object.getClass().getName(), bco); + } + + // 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 000000000..b999a3c1f --- /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 000000000..c7b44c610 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.asset.ModelKey; +import com.jme3.export.JmeImporter; +import com.jme3.export.ReadListener; +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +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 + */ +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 classes + = new HashMap(); + //Key - id, object - the savable + private HashMap contentTable + = new HashMap(); + //Key - savable, object - capsule + private IdentityHashMap capsuleTable + = new IdentityHashMap(); + //Key - id, opject - location in the file + private HashMap locationTable + = new HashMap(); + + public static boolean debug = false; + + private byte[] dataArray; + private int aliasWidth; + + private static final boolean fastRead = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN; + + public BinaryImporter() { + } + + 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(); + + try{ + InputStream is = info.openStream(); + Savable s = load(is); + is.close(); + return s; + }catch (IOException ex){ + logger.log(Level.SEVERE, "An error occured while loading jME binary object", 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 = ByteUtils.readInt(bis); + int bytes = 4; + aliasWidth = ((int)FastMath.log(numClasses, 256) + 1); + + classes.clear(); + for(int i = 0; i < numClasses; i++) { + String alias = readString(bis, aliasWidth); + + int classLength = ByteUtils.readInt(bis); + String className = readString(bis, classLength); + BinaryClassObject bco = new BinaryClassObject(); + bco.alias = alias.getBytes(); + bco.className = className; + + int fields = ByteUtils.readInt(bis); + bytes += (8 + aliasWidth + classLength); + + bco.nameFields = new HashMap(fields); + bco.aliasFields = new HashMap(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.info("Tags: "+numClasses); + logger.info("Objects: "+numLocs); + logger.info("Data Size: "+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; + } + + public BinaryInputCapsule 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; + + BinaryInputCapsule cap = new BinaryInputCapsule(this, bco); + cap.setContent(dataArray, loc, loc+dataLength); + + Savable out = BinaryClassLoader.fromName(bco.className, cap); + + 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 000000000..d68c5ef0c --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java @@ -0,0 +1,1373 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.util.BufferUtils; +import com.jme3.util.IntMap; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +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 HashMap fieldData; + + protected int index = 0; + + public BinaryInputCapsule(BinaryImporter importer, BinaryClassObject bco) { + this.importer = importer; + this.cObj = bco; + } + + public void setContent(byte[] content, int start, int limit) { + fieldData = new HashMap(); + 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 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 readByteBufferArrayList(String name, + ArrayList defVal) throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (ArrayList) 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 readFloatBufferArrayList(String name, + ArrayList defVal) throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (ArrayList) 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 savableArrayListFromArray(Savable[] savables) { + if(savables == null) { + return null; + } + ArrayList arrayList = new ArrayList(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 savableMapFrom2DArray(Savable[][] savables) { + if(savables == null) { + return null; + } + Map map = new HashMap(savables.length); + for (int x = 0; x < savables.length; x++) { + map.put(savables[x][0], savables[x][1]); + } + return map; + } + + private Map stringSavableMapFromKV(String[] keys, Savable[] values) { + if(keys == null || values == null) { + return null; + } + + Map map = new HashMap(keys.length); + for (int x = 0; x < keys.length; x++) + map.put(keys[x], values[x]); + + return map; + } + + private IntMap intSavableMapFromKV(int[] keys, Savable[] values) { + if(keys == null || values == null) { + return null; + } + + IntMap map = new IntMap(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 readSavableMap(String name, Map 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) value; + } + + @SuppressWarnings("unchecked") + public Map readStringSavableMap(String name, Map 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) value; + } + + @SuppressWarnings("unchecked") + public IntMap readIntSavableMap(String name, IntMap 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) 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 + + protected ArrayList readFloatBufferArrayList(byte[] content) + throws IOException { + int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + ArrayList rVal = new ArrayList(length); + for (int x = 0; x < length; x++) { + rVal.add(readFloatBuffer(content)); + } + return rVal; + } + + // ArrayList + + protected ArrayList readByteBufferArrayList(byte[] content) + throws IOException { + int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + ArrayList rVal = new ArrayList(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 readEnum(String name, Class 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/BinaryLoaderModule.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryLoaderModule.java new file mode 100644 index 000000000..eae0be055 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryLoaderModule.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.export.binary; + +import com.jme3.export.InputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + * BinaryLoaderModule defines two methods, the first provides a key value to + * look for to issue the load command. This key is typically (and should be) + * the class name the loader is responsible for. While load handles creating + * a new instance of the class. + * @author mpowell + * + */ +interface BinaryLoaderModule { + + String getKey(); + + /** + * The inputCapsule parameter is not used at all. + * + * The DOMOutputStream class calls this method with a null parameter, so + * if you make use of the parameter, either handle null 'inputCapsule' + * or rearrange the class hierarchy to satisfy DOMOuptutStream. + * + * @param inputCapsule A value which is currently ignored by all + * implementation classes. + */ + Savable load(InputCapsule inputCapsule) throws IOException; +} 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 000000000..209a8ac17 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryOutputCapsule.java @@ -0,0 +1,937 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 array, + String name, ArrayList defVal) throws IOException { + if (array == defVal) + return; + writeAlias(name, BinaryClassField.FLOATBUFFER_ARRAYLIST); + writeFloatBufferArrayList(array); + } + + public void writeByteBufferArrayList(ArrayList array, + String name, ArrayList 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 map, + String name, Map defVal) + throws IOException { + if (map == defVal) + return; + writeAlias(name, BinaryClassField.SAVABLE_MAP); + writeSavableMap(map); + } + + public void writeStringSavableMap(Map map, + String name, Map defVal) + throws IOException { + if (map == defVal) + return; + writeAlias(name, BinaryClassField.STRING_SAVABLE_MAP); + writeStringSavableMap(map); + } + + public void writeIntSavableMap(IntMap map, + String name, IntMap 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); + } + + 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 + + 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 + + protected void writeSavableMap( + Map 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 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 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 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 + + protected void writeFloatBufferArrayList(ArrayList array) + throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.size()); + for (FloatBuffer buf : array) { + write(buf); + } + } + + // ArrayList + + protected void writeByteBufferArrayList(ArrayList 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 000000000..bb835d0ab --- /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; + +/** + * ByteUtils 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 000000000..8236e3acf --- /dev/null +++ b/engine/src/core-plugins/com/jme3/font/plugins/BitmapFontLoader.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.font.*; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.texture.Texture; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class BitmapFontLoader implements AssetLoader { + + public Object load(AssetInfo info) throws IOException { + MaterialDef spriteMat = + (MaterialDef) info.getManager().loadAsset(new AssetKey("Common/MatDefs/Misc/Unshaded.j3md")); + + BitmapCharacterSet charSet = new BitmapCharacterSet(); + Material[] matPages = null; + BitmapFont font = new BitmapFont(); + + String folder = info.getKey().getFolder(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(info.openStream())); + 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 = info.getManager().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); + } + } + reader.close(); + + return font; + } + +} 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 000000000..d8477a46e --- /dev/null +++ b/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.material.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +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.shader.Shader.ShaderType; +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.WrapMode; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Locale; +import java.util.Scanner; + +public class J3MLoader implements AssetLoader { + + private AssetManager owner; + private Scanner scan; + private String fileName; + + private MaterialDef materialDef; + private Material material; + private TechniqueDef technique; + private RenderState renderState; + + private String shaderLang; + private String vertName; + private String fragName; + + 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+"'!"); + } + + private void nextStatement(){ + while (true){ + if (scan.hasNext("\\}")){ + break; + }else if (scan.hasNext("[\n;]")){ + scan.next(); + }else if (scan.hasNext("//")){ + scan.useDelimiter("\n"); + scan.next(); + scan.useDelimiter("\\p{javaWhitespace}+"); + }else{ + break; + } + } + } + + private String readString(String end){ + scan.useDelimiter(end); + String str = scan.next(); + scan.useDelimiter("\\p{javaWhitespace}+"); + return str.trim(); + } + + private Image createColorTexture(ColorRGBA color){ + if (color.getAlpha() == 1.0f){ + // create RGB texture + ByteBuffer data = BufferUtils.createByteBuffer(3); + byte[] bytes = color.asBytesRGBA(); + data.put(bytes[0]).put(bytes[1]).put(bytes[2]); + data.flip(); + + return new Image(Format.RGB8, 1, 1, data); + }else{ + // create RGBA texture + ByteBuffer data = BufferUtils.createByteBuffer(4); + data.putInt(color.asIntRGBA()); + data.flip(); + + return new Image(Format.RGBA8, 1, 1, data); + } + } + + private void readShaderStatement(ShaderType type) throws IOException { + String lang = readString(":"); + + String word = scan.next(); + throwIfNequal(":", word); + + word = readString("[\n;(\\})]"); // new line, semicolon, comment or brace will end a statement + // locate source code + + if (type == ShaderType.Vertex) + vertName = word; + else if (type == ShaderType.Fragment) + fragName = word; + + shaderLang = lang; + } + + private void readLightMode(){ + String mode = readString("[\n;(\\})]"); + LightMode lm = LightMode.valueOf(mode); + technique.setLightMode(lm); + } + + private void readShadowMode(){ + String mode = readString("[\n;(\\})]"); + ShadowMode sm = ShadowMode.valueOf(mode); + technique.setShadowMode(sm); + } + + private void readParam() throws IOException{ + String word = scan.next(); + VarType type; + if (word.equals("Color")){ + type = VarType.Vector4; + }else{ + type = VarType.valueOf(word); + } + + word = readString("[\n;(//)(\\})]"); + FixedFuncBinding ffBinding = null; + if (word.contains(":")){ + // contains fixed func binding + String[] split = word.split(":"); + word = split[0].trim(); + + try { + ffBinding = FixedFuncBinding.valueOf(split[1].trim()); + } catch (IllegalArgumentException ex){ + throw new IOException("FixedFuncBinding '" + + split[1] + "' does not exist!"); + } + } + // TODO: add support for default vals + materialDef.addMaterialParam(type, word, null, ffBinding); + } + + private void readValueParam() throws IOException{ + String name = readString(":"); + throwIfNequal(":", scan.next()); + + // parse value + MatParam p = material.getMaterialDef().getMaterialParam(name); + if (p == null) + throw new IOException("The material parameter: "+name+" is undefined."); + + VarType type = p.getVarType(); + if (type.isTextureType()){ +// String texturePath = readString("[\n;(//)(\\})]"); + String texturePath = readString("[\n;(\\})]"); + 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 key = new TextureKey(texturePath, flipY); + key.setAsCube(type == VarType.TextureCubeMap); + key.setGenerateMips(true); + + Texture tex = owner.loadTexture(key); + if (tex != null){ + if (repeat) + tex.setWrap(WrapMode.Repeat); + + material.setTextureParam(name, type, tex); + } + }else{ + switch (type){ + case Float: + material.setParam(name, type, scan.nextFloat()); + break; + case Vector2: + material.setParam(name, type, new Vector2f(scan.nextFloat(), + scan.nextFloat())); + break; + case Vector3: + material.setParam(name, type, new Vector3f(scan.nextFloat(), + scan.nextFloat(), + scan.nextFloat())); + break; + case Vector4: + material.setParam(name, type, new ColorRGBA(scan.nextFloat(), + scan.nextFloat(), + scan.nextFloat(), + scan.nextFloat())); + break; + case Int: + material.setParam(name, type, scan.nextInt()); + break; + case Boolean: + material.setParam(name, type, scan.nextBoolean()); + break; + default: + throw new UnsupportedOperationException("Unknown type: "+type); + } + } + } + + private void readMaterialParams() throws IOException{ + nextStatement(); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + readParam(); + nextStatement(); + } + } + + private void readExtendingMaterialParams() throws IOException{ + nextStatement(); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + readValueParam(); + nextStatement(); + } + } + + private void readWorldParams() throws IOException{ + nextStatement(); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + word = readString("[\n;(//)(\\})]"); + if (word != null && !word.equals("")){ + technique.addWorldParam(word); + } + nextStatement(); + } + } + + private boolean parseBoolean(String word){ + return word != null && word.equals("On"); + } + + private void readRenderStateStatement() throws IOException{ + String word = scan.next(); + if (word.equals("Wireframe")){ + renderState.setWireframe(parseBoolean(scan.next())); + }else if (word.equals("FaceCull")){ + renderState.setFaceCullMode(FaceCullMode.valueOf(scan.next())); + }else if (word.equals("DepthWrite")){ + renderState.setDepthWrite(parseBoolean(scan.next())); + }else if (word.equals("DepthTest")){ + renderState.setDepthTest(parseBoolean(scan.next())); + }else if (word.equals("Blend")){ + renderState.setBlendMode(BlendMode.valueOf(scan.next())); + }else if (word.equals("AlphaTestFalloff")){ + renderState.setAlphaTest(true); + renderState.setAlphaFallOff(scan.nextFloat()); + }else if (word.equals("PolyOffset")){ + float factor = scan.nextFloat(); + float units = scan.nextFloat(); + renderState.setPolyOffset(factor, units); + }else if (word.equals("ColorWrite")){ + renderState.setColorWrite(parseBoolean(scan.next())); + }else if (word.equals("PointSprite")){ + renderState.setPointSprite(parseBoolean(scan.next())); + }else{ + throwIfNequal(null, word); + } + } + + private void readAdditionalRenderState() throws IOException{ + nextStatement(); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + renderState = material.getAdditionalRenderState(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + readRenderStateStatement(); + nextStatement(); + } + + renderState = null; + } + + private void readRenderState() throws IOException{ + nextStatement(); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + renderState = new RenderState(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + readRenderStateStatement(); + nextStatement(); + } + + technique.setRenderState(renderState); + renderState = null; + } + + private void readDefine(){ + // stops at either next statement or colon + // ways to end a statement: + /* + Block { + Statement + Statement; + Statement //comment + Statement } + */ + String defineName = readString("[\n;:(//)(\\})]"); + if (defineName.equals("")) + return; + + String matParamName = null; + if (scan.hasNext(":")){ + scan.next(); + // this time without colon + matParamName = readString("[\n;(//)(\\})]"); + // add define <-> param mapping + technique.addShaderParamDefine(matParamName, defineName); + }else{ + // add preset define + technique.addShaderPresetDefine(defineName, VarType.Boolean, true); + } + } + + private void readDefines() throws IOException{ + nextStatement(); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + readDefine(); + nextStatement(); + } + + } + + private void readTechniqueStatement() throws IOException{ + String word = scan.next(); + if (word.equals("VertexShader")){ + readShaderStatement(ShaderType.Vertex); + }else if (word.equals("FragmentShader")){ + readShaderStatement(ShaderType.Fragment); + }else if (word.equals("LightMode")){ + readLightMode(); + }else if (word.equals("ShadowMode")){ + readShadowMode(); + }else if (word.equals("WorldParameters")){ + readWorldParams(); + }else if (word.equals("RenderState")){ + readRenderState(); + }else if (word.equals("Defines")){ + readDefines(); + }else{ + throwIfNequal(null, word); + } + nextStatement(); + } + + private void readTransparentStatement() throws IOException{ + String on = readString("[\n;(\\})]"); + material.setTransparent(parseBoolean(on)); + } + + private void readTechnique() throws IOException{ + String name = null; + if (!scan.hasNext("\\{")){ + name = scan.next(); + } + technique = new TechniqueDef(name); + + String word = scan.next(); + throwIfNequal("{", word); + + nextStatement(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + readTechniqueStatement(); + } + + if (vertName != null && fragName != null){ + technique.setShaderFile(vertName, fragName, shaderLang); + } + + materialDef.addTechniqueDef(technique); + technique = null; + vertName = null; + fragName = null; + shaderLang = null; + } + + private void loadFromScanner() throws IOException{ + nextStatement(); + + boolean extending = false; + String name = null; + String word = scan.next(); + if (word.equals("Material")){ + extending = true; + }else if (word.equals("MaterialDef")){ + extending = false; + }else{ + throw new IOException("Specified file is not a Material file"); + } + + nextStatement(); + + word = readString("[(\\{)(//)\n:]"); + if (word == null || word.equals("")) + throw new IOException("Material name cannot be empty"); + + name = word; + + nextStatement(); + + if (scan.hasNext(":")){ + if (!extending){ + throw new IOException("Must use 'Material' when extending."); + } + + scan.next(); // skip colon + String extendedMat = readString("\\{"); + + MaterialDef def = (MaterialDef) owner.loadAsset(new AssetKey(extendedMat)); + if (def == null) + throw new IOException("Extended material "+extendedMat+" cannot be found."); + + material = new Material(def); + }else if (scan.hasNext("\\{")){ + if (extending){ + throw new IOException("Expected ':', got '{'"); + } + materialDef = new MaterialDef(owner, name); + // NOTE: pass file name for defs so they can be loaded later + materialDef.setAssetName(fileName); + } + scan.next(); // skip { + + nextStatement(); + + while (true){ + if (scan.hasNext("\\}")){ + scan.next(); + break; + } + + word = scan.next(); + if (extending){ + if (word.equals("MaterialParameters")){ + readExtendingMaterialParams(); + nextStatement(); + }else if (word.equals("AdditionalRenderState")){ + readAdditionalRenderState(); + nextStatement(); + }else if (word.equals("Transparent")){ + readTransparentStatement(); + nextStatement(); + } + }else{ + if (word.equals("Technique")){ + readTechnique(); + nextStatement(); + }else if (word.equals("MaterialParameters")){ + readMaterialParams(); + nextStatement(); + }else{ + throw new IOException("Expected material statement, got '"+scan.next()+"'"); + } + } + } + } + + public Object load(AssetInfo info) throws IOException { + this.owner = info.getManager(); + + InputStream in = info.openStream(); + try { + scan = new Scanner(in); + scan.useLocale(Locale.US); + this.fileName = info.getKey().getName(); + loadFromScanner(); + } finally { + if (in != null) + in.close(); + } + + if (material != null){ + // 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 000000000..b8f5a1887 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.material.MatParam; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.math.ColorRGBA; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Scanner; + +public class MTLLoader implements AssetLoader { + + protected Scanner scan; + protected MaterialList matList; + protected Material material; + protected AssetManager assetManager; + protected String folderName; + + public void reset(){ + scan = null; + matList = null; + material = null; + } + + protected ColorRGBA readColor(){ + ColorRGBA v = new ColorRGBA(); + v.set(scan.nextFloat(), scan.nextFloat(), scan.nextFloat(), 1.0f); + return v; + } + + protected void nextStatement(){ + scan.useDelimiter("\n"); + scan.next(); + scan.useDelimiter("\\p{javaWhitespace}+"); + } + + protected void startMaterial(String name){ + material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("UseMaterialColors", true); + material.setColor("Ambient", ColorRGBA.DarkGray); + material.setColor("Diffuse", ColorRGBA.White); + material.setColor("Specular", ColorRGBA.Black); + material.setFloat("Shininess", 16f); // prevents "premature culling" bug + matList.put(name, material); + } + + protected boolean readLine(){ + if (!scan.hasNext()){ + return false; + } + + String cmd = scan.next(); + if (cmd.startsWith("#")){ + // skip entire comment until next line + }else if (cmd.equals("newmtl")){ + String name = scan.next(); + startMaterial(name); + }else if (cmd.equals("Ka") || cmd.equals("Ke") || cmd.equals("Ni") || cmd.equals("illum")){ + // ignore it for now + }else if (cmd.equals("Kd")){ + ColorRGBA color = readColor(); + MatParam param = material.getParam("Diffuse"); + if (param != null){ + color.a = ((ColorRGBA) param.getValue()).getAlpha(); + } + material.setColor("Diffuse", color); + }else if (cmd.equals("Ks")){ + material.setColor("Specular", readColor()); + }else if (cmd.equals("Ns")){ + material.setFloat("Shininess", scan.nextFloat() /* (128f / 1000f)*/ ); + }else if (cmd.equals("d")){ + float alpha = scan.nextFloat(); +// if (alpha < 1f){ +// MatParam param = material.getParam("Diffuse"); +// ColorRGBA color; +// if (param != null) +// color = (ColorRGBA) param.getValue(); +// else +// color = new ColorRGBA(ColorRGBA.White); +// +// color.a = scan.nextFloat(); +// material.setColor("Diffuse", color); +// material.setBoolean("UseAlpha", true); +// material.setTransparent(true); +// material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); +// } + }else if (cmd.equals("map_Ka")){ + // ignore it for now + }else if (cmd.equals("map_Kd")){ + String path = scan.next(); + String name = new File(path).getName(); + TextureKey key = new TextureKey(folderName + name); + key.setGenerateMips(true); + Texture texture = assetManager.loadTexture(key); + if (texture != null){ + texture.setWrap(WrapMode.Repeat); + material.setTexture("DiffuseMap", texture); + } + }else if (cmd.equals("map_bump") || cmd.equals("bump")){ + if (material.getParam("NormalMap") == null){ + String path = scan.next(); + String name = new File(path).getName(); + TextureKey key = new TextureKey(folderName + name); + key.setGenerateMips(true); + Texture texture = assetManager.loadTexture(key); + if (texture != null){ + texture.setWrap(WrapMode.Repeat); + material.setTexture("NormalMap", texture); + if (texture.getImage().getFormat() == Format.LATC){ + material.setBoolean("LATC", true); + } + } + } + }else if (cmd.equals("map_Ks")){ + String path = scan.next(); + String name = new File(path).getName(); + TextureKey key = new TextureKey(folderName + name); + key.setGenerateMips(true); + Texture texture = assetManager.loadTexture(key); + if (texture != null){ + texture.setWrap(WrapMode.Repeat); + material.setTexture("SpecularMap", texture); + + // NOTE: since specular color is modulated with specmap + // make sure we have it set + material.setColor("Specular", ColorRGBA.White); + } + }else if (cmd.equals("map_d")){ + String path = scan.next(); + String name = new File(path).getName(); + TextureKey key = new TextureKey(folderName + name); + key.setGenerateMips(true); + Texture texture = assetManager.loadTexture(key); + if (texture != null){ + texture.setWrap(WrapMode.Repeat); + material.setTexture("AlphaMap", texture); + material.setTransparent(true); + material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + material.getAdditionalRenderState().setAlphaTest(true); + material.getAdditionalRenderState().setAlphaFallOff(0.01f); + } + }else{ + System.out.println("Unknown statement in MTL! "+cmd); + } + nextStatement(); + + return true; + } + + @SuppressWarnings("empty-statement") + public Object load(AssetInfo info){ + this.assetManager = info.getManager(); + folderName = info.getKey().getFolder(); + + InputStream in = info.openStream(); + scan = new Scanner(in); + scan.useLocale(Locale.US); + + matList = new MaterialList(); + while (readLine()); + MaterialList list = matList; + + reset(); + + try{ + in.close(); + }catch (IOException ex){ + } + 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 000000000..99202d4b3 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.util.*; +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.Mesh.Mode; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.mesh.IndexIntBuffer; +import com.jme3.scene.mesh.IndexShortBuffer; +import java.io.IOException; +import java.io.InputStream; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map.Entry; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * Reads OBJ format models. + */ +public final class OBJLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(OBJLoader.class.getName()); + + protected final ArrayList verts = new ArrayList(); + protected final ArrayList texCoords = new ArrayList(); + protected final ArrayList norms = new ArrayList(); + protected final ArrayList faces = new ArrayList(); + protected final HashMap> matFaces = new HashMap>(); + protected String currentMatName; + + protected final HashMap vertIndexMap = new HashMap(); + protected final IntMap indexVertMap = new IntMap(); + protected int curIndex = 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; + } + + public void reset(){ + verts.clear(); + texCoords.clear(); + norms.clear(); + faces.clear(); + matFaces.clear(); + + vertIndexMap.clear(); + indexVertMap.clear(); + + currentMatName = 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 vertList = new ArrayList(); + + protected void readFace(){ + Face f = new Face(); + vertList.clear(); + + String line = scan.nextLine().trim(); + String[] verticies = line.split(" "); + 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]); + }else if (split.length == 2){ + v = Integer.parseInt(split[0]); + vt = Integer.parseInt(split[1]); + }else if (split.length == 3 && !split[1].equals("")){ + v = Integer.parseInt(split[0]); + vt = Integer.parseInt(split[1]); + vn = Integer.parseInt(split[2]); + }else if (split.length == 3){ + v = Integer.parseInt(split[0]); + vn = Integer.parseInt(split[2]); + } + + 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.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(" "); + v.setX( Float.parseFloat(split[0]) ); + v.setY( Float.parseFloat(split[1]) ); + +// 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); + + matList = (MaterialList) assetManager.loadAsset(key.getFolder() + name); + + if (matList != null){ + // create face lists for every material + for (String matName : matList.keySet()){ + matFaces.put(matName, new ArrayList()); + } + }else{ + logger.log(Level.WARNING, "Can't find MTL file. " + + "Using default material for OBJ."); + } + } + + private static final Pattern nl = Pattern.compile("\n"); + private static final Pattern ws = Pattern.compile("\\p{javaWhitespace}+"); + + protected void nextStatement(){ + scan.useDelimiter(nl); + scan.next(); + scan.useDelimiter(ws); + } + + protected boolean readLine() throws IOException{ + if (!scan.hasNext()){ + return false; + } + + String cmd = scan.next(); + if (cmd.startsWith("#")){ + // skip entire comment until next line + 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(); + }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")){ + nextStatement(); + }else{ + // skip entire command until next line + System.out.println("Unknown statement in OBJ! "+cmd); + nextStatement(); + } + + return true; + } + + protected Geometry createGeometry(ArrayList faceList, String matName) throws IOException{ + if (faceList.size() == 0) + 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); + + return geom; + } + + protected Mesh constructMesh(ArrayList faceList){ + Mesh m = new Mesh(); + m.setMode(Mode.Triangles); + + boolean hasTexCoord = false; + boolean hasNormals = false; + + ArrayList newFaces = new ArrayList(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{ + key = (ModelKey) info.getKey(); + assetManager = info.getManager(); + + if (!(info.getKey() instanceof ModelKey)) + throw new IllegalArgumentException("Model assets must be loaded using a ModelKey"); + + InputStream in = info.openStream(); + scan = new Scanner(in); + scan.useLocale(Locale.US); + + 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"); + + while (readLine()); + + if (matFaces.size() > 0){ + for (Entry> entry : matFaces.entrySet()){ + ArrayList 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); + } + + reset(); + + try{ + in.close(); + }catch (IOException ex){ + } + + 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/texture/plugins/DDSLoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java new file mode 100644 index 000000000..1ff8b250b --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java @@ -0,0 +1,661 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.*; +import com.jme3.util.*; +import com.jme3.asset.AssetLoader; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * + * DDSLoader 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 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 = info.openStream(); + in = new LittleEndien(stream); + loadHeader(); + ArrayList data = readData( ((TextureKey)info.getKey()).isFlipY() ); + stream.close(); + + return new Image(pixelFormat, width, height, 0, data, sizes); + } + + public Image load(InputStream stream) throws IOException{ + in = new LittleEndien(stream); + loadHeader(); + ArrayList data = readData(false); + return new Image(pixelFormat, width, height, 0, 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){ + // mark texture as 3D + } + 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); + + 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 + } + } + + 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.warning("Got " + mipMapCount + "mipmaps, expected" + 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.warning("Expected size = " + size + ", real = " + 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.warning("Expected size = " + size + ", real = " + 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; + + int offset = 0; + 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); + } + + offset += sizes[mip]; + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + buffer.rewind(); + + return buffer; + } + + /** + * 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 readData(boolean flip) throws IOException { + int totalSize = 0; + + for (int i = 0; i < sizes.length; i++) { + totalSize += sizes[i]; + } + + ArrayList allMaps = new ArrayList(); + if (depth > 1){ + 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 (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 final 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 final String string(int value) { + StringBuffer buf = new StringBuffer(); + + 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 000000000..1d3506fbc --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/DXTFlipper.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.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; + +/** + * + * @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; + } + + public 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]; + } + + public 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 + */ + public static void flipDXT1Block(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 + flipDXT1Block(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 + flipDXT1Block(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 000000000..bca3ff2b1 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.*; +import com.jme3.util.*; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +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 final 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 final 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 final 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 final 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{ + StringBuffer sb = new StringBuffer(); + 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(" "); + 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.fine("Ignored string: "+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.warning("HDR Command ignored: "+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 = info.openStream(); + Image img = load(in, flip); + in.close(); + return img; + } + +} 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 000000000..a8af2f322 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/PFMLoader.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.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +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{ + StringBuffer sb = new StringBuffer(); + 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; + } + } + + 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 = info.openStream(); + 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(); + boolean needYFlip = ((TextureKey)info.getKey()).isFlipY(); + + // 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); + } + +} 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 000000000..f0f5910d2 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.asset.AssetLoader; +import com.jme3.texture.Image; +import com.jme3.util.BufferUtils; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.TextureKey; +import com.jme3.texture.Image.Format; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * TextureManager provides static methods for building a + * Texture 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 = info.openStream(); + Image img = load(in, flip); +// in.close(); + return img; + } + + /** + * loadImage is a manual image loader which is entirely + * independent of AWT. OUT: RGB888 or RGBA8888 Image object + * + * @return Image 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 = null; + 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 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 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 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 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 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 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; + } + + + 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; + } + + /** + * flipEndian 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 000000000..77d8ebe1d --- /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 extends clauses, and implicit upper bounds + * when no explicit extends or super clause is + * present. */ + // Especially useful for parametrized 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 000000000..c6fa2c25e --- /dev/null +++ b/engine/src/core/checkers/quals/DefaultQualifier.java @@ -0,0 +1,42 @@ +package checkers.quals; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +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}. + *

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

+ * + * 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 000000000..619a751d8 --- /dev/null +++ b/engine/src/core/checkers/quals/DefaultQualifierInHierarchy.java @@ -0,0 +1,27 @@ +package checkers.quals; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; + +import java.lang.annotation.*; + +/** + * Indicates that the annotated qualifier is the default qualifier in the + * qualifier hierarchy: it applies if the programmer writes no explicit + * qualifier. + *

+ * + * The {@link DefaultQualifier} annotation, which targets Java code elements, + * takes precedence over {@code DefaultQualifierInHierarchy}. + *

+ * + * 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 000000000..bf3349e23 --- /dev/null +++ b/engine/src/core/checkers/quals/DefaultQualifiers.java @@ -0,0 +1,33 @@ +package checkers.quals; + +import static java.lang.annotation.ElementType.*; + +import java.lang.annotation.*; + +/** + * 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: + * + *

+ *   @DefaultQualifiers({
+ *       @DefaultQualifier("NonNull"),
+ *       @DefaultQualifier(value = "Interned", locations = ALL_EXCEPT_LOCALS),
+ *       @DefaultQualifier("Tainted")
+ *   })
+ * 
+ * + * @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 000000000..8c96cf66d --- /dev/null +++ b/engine/src/core/checkers/quals/Dependent.java @@ -0,0 +1,35 @@ +package checkers.quals; + +import java.lang.annotation.*; + +/** + * 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. + * + *

Example: + * 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: + * + *


+ *   private @Nullable @Dependent(result=NonNull.class, when=ThreadSafe.class)
+ *     Lock lock;
+ * 
+ */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +//@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +public @interface Dependent { + + /** + * The class of the refined qualifier to be applied. + */ + Class result(); + + /** + * The qualifier class of the receiver that causes the {@code result} + * qualifier to be applied. + */ + Class when(); +} diff --git a/engine/src/core/checkers/quals/SubtypeOf.java b/engine/src/core/checkers/quals/SubtypeOf.java new file mode 100644 index 000000000..ffc8be045 --- /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}.) + * + *

+ * Example: + *

 @SubtypeOf( { Nullable.class } )
+ * public @interface NonNull { }
+ * 
+ * + *

+ * + * If a qualified type is a subtype of the same type without any qualifier, + * then use Unqualified.class in place of a type qualifier + * class. For example, to express that @Encrypted C + * is a subtype of C (for every class + * C), and likewise for @Interned, write: + * + *

 @SubtypeOf(Unqualified.class)
+ * public @interface Encrypted { }
+ *
+ * @SubtypeOf(Unqualified.class)
+ * public @interface Interned { }
+ * 
+ * + *

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

 @SubtypeOf( { } )
+ * public @interface Nullable { }
+ *
+ * @SubtypeOf( {} )
+ * public @interface ReadOnly { }
+ * 
+ * + *

+ * 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[] value(); +} diff --git a/engine/src/core/checkers/quals/TypeQualifier.java b/engine/src/core/checkers/quals/TypeQualifier.java new file mode 100644 index 000000000..25c4f7bbd --- /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 000000000..a210b704b --- /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. + * + *

+ * 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 000000000..98d99fa68 --- /dev/null +++ b/engine/src/core/checkers/quals/Unused.java @@ -0,0 +1,44 @@ +package checkers.quals; + +import static java.lang.annotation.ElementType.*; + +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. + * + *

Example + * 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: + * + *


+ * class Table {
+ *   private @Unused(when=LocalToThread.class) final Lock lock;
+ *   ...
+ * }
+ * 
+ * + * The checker for {@code @LocalToThread} would issue an error for the following code: + * + *
  @LocalToThread Table table = ...;
+ *   ... table.lock ...;
+ * 
+ * + */ +@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 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 000000000..5ae80a7d6 --- /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 000000000..efa4da032 --- /dev/null +++ b/engine/src/core/com/jme3/animation/AnimChannel.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 java.util.BitSet; + +/** + * AnimChannel 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 AnimControl control; + +// private ArrayList affectedBones; + private BitSet affectedBones; + + private BoneAnimation animation; + private BoneAnimation blendFrom; + private float time; + private float speed; + private float timeBlendFrom; + private float speedBlendFrom; + + private LoopMode loopMode, loopModeBlendFrom; + private float defaultBlendTime = 0.15f; + + private float blendAmount = 1f; + private float blendRate = 0; + + private static float clampWrapTime(float t, float max, LoopMode loopMode){ + 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; + } + + /** + * @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 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. + * This resets the time to zero, and optionally blends the animation + * over blendTime 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"); + + BoneAnimation 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; + } + + /** + * + * @param name + */ + public void setAnim(String name){ + setAnim(name, defaultBlendTime); + } + + /** + * 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); + } + } + + + void reset(){ + animation = null; + blendFrom = null; + } + + void update(float tpf) { + if (animation == null) + return; + + if (blendFrom != null){ + 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, 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; + } + + + } + + public AnimControl getControl() { + return control; + } + + + +} 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 000000000..587db4b01 --- /dev/null +++ b/engine/src/core/com/jme3/animation/AnimControl.java @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +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; +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; +import java.util.Collection; +import java.util.HashMap; + +/** + * AnimControl 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 Savable, Cloneable { + + /** + * List of targets which this controller effects. + */ + Mesh[] targets; + + /** + * Skeleton object must contain corresponding data for the targets' weight buffers. + */ + Skeleton skeleton; + + /** + * List of animations + */ + HashMap animationMap; + + /** + * Animation channels + */ + transient ArrayList channels + = new ArrayList(); + + transient ArrayList listeners + = new ArrayList(); + + /** + * Create a new AnimControl that will animate the given skins + * using the skeleton and provided animations. + * + * @param model The root node of all the skins specified in + * meshes argument. + * @param meshes The skins, or meshes, to animate. Should have + * properly set BoneIndex and BoneWeight buffers. + * @param skeleton The skeleton structure represents a bone hierarchy + * to be animated. + */ + public AnimControl(Node model, Mesh[] meshes, Skeleton skeleton){ + super(model); + + this.skeleton = skeleton; + this.targets = meshes; + reset(); + } + + /** + * Used only for Saving/Loading models (all parameters of the non-default + * constructor are restored from the saved model, but the object must be + * constructed beforehand) + */ + public AnimControl() { + } + + public Control cloneForSpatial(Spatial spatial){ + try { + Node clonedNode = (Node) spatial; + AnimControl clone = (AnimControl) super.clone(); + clone.spatial = spatial; + clone.skeleton = new Skeleton(skeleton); + Mesh[] meshes = new Mesh[targets.length]; + for (int i = 0; i < meshes.length; i++) { + meshes[i] = ((Geometry) clonedNode.getChild(i)).getMesh(); + } + for (int i = meshes.length; 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); + } + } + } + clone.targets = meshes; + clone.channels = new ArrayList(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * @param animations Set the animations that this AnimControl + * will be capable of playing. The animations should be compatible + * with the skeleton given in the constructor. + */ + public void setAnimations(HashMap 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 BoneAnimation getAnim(String name){ + return animationMap.get(name); + } + + /** + * Adds an animation to be available for playing to this + * AnimControl. + * @param anim The animation to add. + */ + public void addAnim(BoneAnimation anim){ + 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(BoneAnimation anim){ + if (!animationMap.containsKey(anim.getName())) + throw new IllegalArgumentException("Given animation does not exist " + + "in this AnimControl"); + + animationMap.remove(anim.getName()); + } + + 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; + } + + /** + * Create a new animation channel, by default assigned to all bones + * in the skeleton. + * + * @return A new animation channel for this AnimControl. + */ + 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 AnimChannel. + * @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 + * AnimControl. + * + * @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 AnimControl. + */ + public Skeleton getSkeleton() { + return skeleton; + } + + /** + * @return The targets, or skins, being influenced by this + * AnimControl. + */ + public Mesh[] getTargets() { + return targets; + } + + /** + * 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 AnimControl + * + * @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); + } + } + + final void reset(){ + resetToBind(); + if (skeleton != null){ + skeleton.resetAndUpdate(); + } + } + + void resetToBind(){ + for (int i = 0; i < targets.length; i++){ + Mesh mesh = targets[i]; + if (targets[i].getBuffer(Type.BindPosePosition) != null){ + 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(); + pb.put(bpb).clear(); + nb.put(bnb).clear(); + } + } + } + + /** + * @return The names of all animations that this AnimControl + * can play. + */ + public Collection 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){ + BoneAnimation a = animationMap.get(name); + if (a == null) + throw new IllegalArgumentException("The animation " + name + + " does not exist in this AnimControl"); + + return a.getLength(); + } + + @Override + protected void controlUpdate(float tpf) { + resetToBind(); // reset morph meshes to bind pose + skeleton.reset(); // reset skeleton to bind pose + + for (int i = 0; i < channels.size(); i++){ + channels.get(i).update(tpf); + } + + skeleton.updateWorldVectors(); + // here update the targets verticles if no hardware skinning supported + + 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++){ + // only update targets with bone-vertex assignments + if (targets[i].getBuffer(Type.BoneIndex) != null) + softwareSkinUpdate(targets[i], offsetMatrices); + } + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + + private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices){ + int maxWeightsPerVert = mesh.getMaxNumWeights(); + 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 * 3; + 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); + } + + vb.updateData(fvb); + nb.updateData(fnb); + +// mesh.updateBound(); + } + + @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); + oc.writeStringSavableMap(animationMap, "animations", 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); + animationMap = (HashMap) in.readStringSavableMap("animations", null); + } + +} 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 000000000..22a0b98b2 --- /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; + +/** + * AnimEventListener 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/AnimationPath.java b/engine/src/core/com/jme3/animation/AnimationPath.java new file mode 100644 index 000000000..5870e4308 --- /dev/null +++ b/engine/src/core/com/jme3/animation/AnimationPath.java @@ -0,0 +1,885 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.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.math.FastMath; +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.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.scene.shape.Box; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * + * + * @deprecated use {@link MotionPath} + */ +@Deprecated +public class AnimationPath extends AbstractControl { + + private boolean playing = false; + private int currentWayPoint; + private float currentValue; + private List wayPoints = new ArrayList(); + private Node debugNode; + private AssetManager assetManager; + private List listeners; + private Vector3f curveDirection; + private Vector3f lookAt; + private Vector3f upVector; + private Quaternion rotation; + private float duration = 5f; + private List segmentsLength; + private float totalLength; + private List CRcontrolPoints; + private float speed; + private float curveTension = 0.5f; + private boolean loop = false; + private boolean cycle = false; + + @Override + protected void controlUpdate(float tpf) { + + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + + } + + /** + * Enum for the different type of target direction behavior + */ + @Deprecated + 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 + } + private Direction directionType = Direction.None; + + @Deprecated + public enum PathInterpolation { + + /** + * Compute a linear path between the waypoints + */ + Linear, + /** + * Compute a Catmull-Rom spline path between the waypoints + * see http://www.mvps.org/directx/articles/catmull/ + */ + CatmullRom + } + private PathInterpolation pathInterpolation = PathInterpolation.CatmullRom; + + /** + * Create an animation Path for this target + * @param target + */ + @Deprecated + public AnimationPath(Spatial target) { + super(); + this.spatial = target; + target.addControl(this); + } + + /** + * don't use this contructor use AnimationPath(Spatial target) + */ + @Deprecated + public AnimationPath() { + super(); + } + + @Deprecated + public Control cloneForSpatial(Spatial spatial) { + AnimationPath path = new AnimationPath(spatial); + for (Iterator it = wayPoints.iterator(); it.hasNext();) { + Vector3f vector3f = it.next(); + path.addWayPoint(vector3f); + } + for (Iterator it = listeners.iterator(); it.hasNext();) { + AnimationPathListener animationPathListener = it.next(); + path.addListener(animationPathListener); + } + return path; + } + + @Override + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public boolean isEnabled() { + return enabled; + } + float eps = 0.0001f; + + /** + * Cal each update, don't call this method, it's for internal use only + * @param tpf + */ + @Override + public void update(float tpf) { + + if (enabled) { + if (playing) { + spatial.setLocalTranslation(interpolatePath(tpf)); + computeTargetDirection(); + + if (currentValue >= 1.0f) { + currentValue = 0; + currentWayPoint++; + triggerWayPointReach(currentWayPoint); + } + if (currentWayPoint == wayPoints.size() - 1) { + if (loop) { + currentWayPoint = 0; + } else { + stop(); + } + } + } + } + } + + private Vector3f interpolatePath(float tpf) { + Vector3f temp = null; + float val; + switch (pathInterpolation) { + case CatmullRom: + + val = tpf * speed; + currentValue += eps; + temp = FastMath.interpolateCatmullRom(currentValue, curveTension, CRcontrolPoints.get(currentWayPoint), CRcontrolPoints.get(currentWayPoint + 1), CRcontrolPoints.get(currentWayPoint + 2), CRcontrolPoints.get(currentWayPoint + 3)); + float dist = temp.subtract(spatial.getLocalTranslation()).length(); + + while (dist < val) { + currentValue += eps; + temp = FastMath.interpolateCatmullRom(currentValue, curveTension, CRcontrolPoints.get(currentWayPoint), CRcontrolPoints.get(currentWayPoint + 1), CRcontrolPoints.get(currentWayPoint + 2), CRcontrolPoints.get(currentWayPoint + 3)); + dist = temp.subtract(spatial.getLocalTranslation()).length(); + } + if (directionType == Direction.Path || directionType == Direction.PathAndRotation) { + curveDirection = temp.subtract(spatial.getLocalTranslation()).normalizeLocal(); + } + break; + case Linear: + val = duration * segmentsLength.get(currentWayPoint) / totalLength; + currentValue = Math.min(currentValue + tpf / val, 1.0f); + temp = FastMath.interpolateLinear(currentValue, wayPoints.get(currentWayPoint), wayPoints.get(currentWayPoint + 1)); + curveDirection = wayPoints.get(currentWayPoint + 1).subtract(wayPoints.get(currentWayPoint)).normalizeLocal(); + break; + default: + break; + } + return temp; + } + + private void computeTargetDirection() { + switch (directionType) { + case Path: + Quaternion q = new Quaternion(); + q.lookAt(curveDirection, 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(curveDirection, Vector3f.UNIT_Y); + q2.multLocal(rotation); + spatial.setLocalRotation(q2); + } + break; + case Rotation: + if (rotation != null) { + spatial.setLocalRotation(rotation); + } + break; + case None: + break; + default: + break; + } + } + + private void attachDebugNode(Node root) { + if (debugNode == null) { + debugNode = new Node("AnimationPathFor" + spatial.getName()); + Material m = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + for (Iterator it = wayPoints.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 (pathInterpolation) { + 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/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + + float[] array = new float[wayPoints.size() * 3]; + short[] indices = new short[(wayPoints.size() - 1) * 2]; + int i = 0; + int cpt = 0; + int k = 0; + int j = 0; + for (Iterator it = wayPoints.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()) { + k = j; + indices[cpt] = (short) k; + cpt++; + k++; + indices[cpt] = (short) k; + cpt++; + j++; + } + } + + Mesh lineMesh = new Mesh(); + lineMesh.setMode(Mesh.Mode.Lines); + lineMesh.setBuffer(VertexBuffer.Type.Position, 3, array); + lineMesh.setBuffer(VertexBuffer.Type.Index, (wayPoints.size() - 1) * 2, indices); + lineMesh.updateBound(); + lineMesh.updateCounts(); + + Geometry lineGeometry = new Geometry("line", lineMesh); + lineGeometry.setMaterial(mat); + return lineGeometry; + } + + private Geometry CreateCatmullRomPath() { + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + int nbSubSegments = 10; + + float[] array = new float[(((wayPoints.size() - 1) * nbSubSegments) + 1) * 3]; + short[] indices = new short[((wayPoints.size() - 1) * nbSubSegments) * 2]; + int i = 0; + int cptCP = 0; + for (Iterator it = wayPoints.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++) { + Vector3f temp = FastMath.interpolateCatmullRom((float) j / nbSubSegments, curveTension, CRcontrolPoints.get(cptCP), + CRcontrolPoints.get(cptCP + 1), CRcontrolPoints.get(cptCP + 2), CRcontrolPoints.get(cptCP + 3)); + array[i] = temp.x; + i++; + array[i] = temp.y; + i++; + array[i] = temp.z; + i++; + } + } + cptCP++; + } + + i = 0; + int k = 0; + for (int j = 0; j < ((wayPoints.size() - 1) * nbSubSegments); j++) { + k = j; + indices[i] = (short) k; + i++; + k++; + indices[i] = (short) k; + i++; + } + + + + Mesh lineMesh = new Mesh(); + lineMesh.setMode(Mesh.Mode.Lines); + lineMesh.setBuffer(VertexBuffer.Type.Position, 3, array); + lineMesh.setBuffer(VertexBuffer.Type.Index, ((wayPoints.size() - 1) * nbSubSegments) * 2, indices); + lineMesh.updateBound(); + lineMesh.updateCounts(); + + Geometry lineGeometry = new Geometry("line", lineMesh); + lineGeometry.setMaterial(mat); + return lineGeometry; + } + + private void initCatmullRomWayPoints(List list) { + if (CRcontrolPoints == null) { + CRcontrolPoints = new ArrayList(); + } 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 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)))); + } + + } + + @Override + public void render(RenderManager rm, ViewPort vp) { + //nothing to render + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList((ArrayList) wayPoints, "wayPoints", null); + oc.write(lookAt, "lookAt", Vector3f.ZERO); + oc.write(upVector, "upVector", Vector3f.UNIT_Y); + oc.write(rotation, "rotation", Quaternion.IDENTITY); + oc.write(duration, "duration", 5f); + oc.write(directionType, "directionType", Direction.None); + oc.write(pathInterpolation, "pathInterpolation", PathInterpolation.CatmullRom); + float list[]=new float[segmentsLength.size()]; + for (int i=0;i) in.readSavableArrayList("wayPoints", null); + lookAt = (Vector3f) in.readSavable("lookAt", Vector3f.ZERO); + upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y); + rotation = (Quaternion) in.readSavable("rotation", Quaternion.IDENTITY); + duration = in.readFloat("duration", 5f); + float list[]=in.readFloatArray("segmentsLength", null); + if (list!=null){ + segmentsLength=new ArrayList(); + for (int i=0;i) in.readSavableArrayList("CRControlPoints", null); + speed = in.readFloat("speed", 0); + curveTension = in.readFloat("curveTension", 0.5f); + cycle = in.readBoolean("cycle", false); + loop = in.readBoolean("loop", false); + } + + /** + * plays the animation + */ + @Deprecated + public void play() { + playing = true; + } + + /** + * pauses the animation + */ + @Deprecated + public void pause() { + playing = false; + } + + /** + * stops the animation, next time play() is called the animation will start from the begining. + */ + @Deprecated + public void stop() { + playing = false; + currentWayPoint = 0; + } + + /** + * Addsa waypoint to the path + * @param wayPoint a position in world space + */ + @Deprecated + public void addWayPoint(Vector3f wayPoint) { + if (wayPoints.size() > 2 && this.cycle) { + wayPoints.remove(wayPoints.size() - 1); + } + wayPoints.add(wayPoint); + if (wayPoints.size() >= 2 && this.cycle) { + wayPoints.add(wayPoints.get(0)); + } + if (wayPoints.size() > 1) { + computeTotalLentgh(); + } + } + + + private void computeTotalLentgh() { + totalLength = 0; + float l = 0; + if (segmentsLength == null) { + segmentsLength = new ArrayList(); + } else { + segmentsLength.clear(); + } + if (pathInterpolation == PathInterpolation.Linear) { + if (wayPoints.size() > 1) { + for (int i = 0; i < wayPoints.size() - 1; i++) { + l = wayPoints.get(i + 1).subtract(wayPoints.get(i)).length(); + segmentsLength.add(l); + totalLength += l; + } + } + } else { + initCatmullRomWayPoints(wayPoints); + computeCatmulLength(); + } + } + + private void computeCatmulLength() { + float l = 0; + if (wayPoints.size() > 1) { + for (int i = 0; i < wayPoints.size() - 1; i++) { + l = getCatmullRomP1toP2Length(CRcontrolPoints.get(i), + CRcontrolPoints.get(i + 1), CRcontrolPoints.get(i + 2), CRcontrolPoints.get(i + 3), 0, 1); + segmentsLength.add(l); + totalLength += l; + } + } + speed = totalLength / duration; + } + + /** + * retruns the length of the path in world units + * @return the length + */ + @Deprecated + public float getLength() { + return totalLength; + } + //Compute lenght of p1 to p2 arc segment + //TODO extract to FastMath class + + private float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, float startRange, float endRange) { + + float epsilon = 0.001f; + float middleValue = (startRange + endRange) * 0.5f; + Vector3f start = p1; + if (startRange != 0) { + start = FastMath.interpolateCatmullRom(startRange, curveTension, p0, p1, p2, p3); + } + Vector3f end = p2; + if (endRange != 1) { + end = FastMath.interpolateCatmullRom(endRange, curveTension, p0, p1, p2, p3); + } + 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); + l2 = getCatmullRomP1toP2Length(p0, p1, p2, p3, middleValue, endRange); + } + l = l1 + l2; + return l; + } + + /** + * returns the waypoint at the given index + * @param i the index + * @return returns the waypoint position + */ + @Deprecated + public Vector3f getWayPoint(int i) { + return wayPoints.get(i); + } + + /** + * remove the waypoint from the path + * @param wayPoint the waypoint to remove + */ + @Deprecated + public void removeWayPoint(Vector3f wayPoint) { + wayPoints.remove(wayPoint); + if (wayPoints.size() > 1) { + computeTotalLentgh(); + } + } + + /** + * remove the waypoint at the given index from the path + * @param i the index of the waypoint to remove + */ + @Deprecated + public void removeWayPoint(int i) { + removeWayPoint(wayPoints.get(i)); + } + + /** + * returns an iterator on the waypoints collection + * @return + */ + @Deprecated + public Iterator iterator() { + return wayPoints.iterator(); + } + + /** + * return the type of path interpolation for this path + * @return the path interpolation + */ + @Deprecated + public PathInterpolation getPathInterpolation() { + return pathInterpolation; + } + + /** + * sets the path interpolation for this path + * @param pathInterpolation + */ + @Deprecated + public void setPathInterpolation(PathInterpolation pathInterpolation) { + this.pathInterpolation = pathInterpolation; + computeTotalLentgh(); + if (debugNode != null) { + Node parent = debugNode.getParent(); + debugNode.removeFromParent(); + debugNode.detachAllChildren(); + debugNode = null; + attachDebugNode(parent); + } + } + + /** + * disable the display of the path and the waypoints + */ + @Deprecated + 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 + */ + @Deprecated + public void enableDebugShape(AssetManager manager, Node rootNode) { + assetManager = manager; + computeTotalLentgh(); + attachDebugNode(rootNode); + } + + /** + * Adds an animation pathListener to the path + * @param listener the AnimationPathListener to attach + */ + @Deprecated + public void addListener(AnimationPathListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + /** + * remove the given listener + * @param listener the listener to remove + */ + @Deprecated + public void removeListener(AnimationPathListener listener) { + if (listeners != null) { + listeners.remove(listener); + } + } + + /** + * return the number of waypoints of this path + * @return + */ + @Deprecated + public int getNbWayPoints() { + return wayPoints.size(); + } + + private void triggerWayPointReach(int wayPointIndex) { + for (Iterator it = listeners.iterator(); it.hasNext();) { + AnimationPathListener listener = it.next(); + listener.onWayPointReach(this, wayPointIndex); + } + } + + /** + * returns the direction type of the target + * @return the direction type + */ + @Deprecated + 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 + */ + @Deprecated + 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 + */ + @Deprecated + public void setLookAt(Vector3f lookAt, Vector3f upVector) { + this.lookAt = lookAt; + this.upVector = upVector; + } + + /** + * returns the rotation of the target + * @return the rotation quaternion + */ + @Deprecated + 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 + */ + @Deprecated + public void setRotation(Quaternion rotation) { + this.rotation = rotation; + } + + @Deprecated + public float getDuration() { + return duration; + } + + /** + * Sets the duration of the animation + * @param duration + */ + @Deprecated + public void setDuration(float duration) { + this.duration = duration; + speed = totalLength / duration; + } + + @Deprecated + public float getCurveTension() { + return curveTension; + } + + /** + * sets the tension of the curve (only for catmull rom) 0.0 will give a linear curve, 1.0 a round curve + * @param curveTension + */ + @Deprecated + public void setCurveTension(float curveTension) { + this.curveTension = curveTension; + computeTotalLentgh(); + if (debugNode != null) { + Node parent = debugNode.getParent(); + debugNode.removeFromParent(); + debugNode.detachAllChildren(); + debugNode = null; + attachDebugNode(parent); + } + } + + /** + * Sets the path to be a cycle + * @param cycle + */ + @Deprecated + public void setCycle(boolean cycle) { + + if (wayPoints.size() >= 2) { + if (this.cycle && !cycle) { + wayPoints.remove(wayPoints.size() - 1); + } + if (!this.cycle && cycle) { + wayPoints.add(wayPoints.get(0)); + System.out.println("adding first wp"); + } + this.cycle = cycle; + computeTotalLentgh(); + if (debugNode != null) { + Node parent = debugNode.getParent(); + debugNode.removeFromParent(); + debugNode.detachAllChildren(); + debugNode = null; + attachDebugNode(parent); + } + } else { + this.cycle = cycle; + } + } + + @Deprecated + public boolean isCycle() { + return cycle; + } + + /** + * returs true is the animation loops + * @return + */ + @Deprecated + public boolean isLoop() { + return loop; + } + + /** + * Loops the animation + * @param loop + */ + @Deprecated + public void setLoop(boolean loop) { + this.loop = loop; + } + + @Override + public String toString() { + return "AnimationPath{" + "playing=" + playing + "currentWayPoint=" + currentWayPoint + "currentValue=" + currentValue + "wayPoints=" + wayPoints + "debugNode=" + debugNode + "assetManager=" + assetManager + "listeners=" + listeners + "curveDirection=" + curveDirection + "lookAt=" + lookAt + "upVector=" + upVector + "rotation=" + rotation + "duration=" + duration + "segmentsLength=" + segmentsLength + "totalLength=" + totalLength + "CRcontrolPoints=" + CRcontrolPoints + "speed=" + speed + "curveTension=" + curveTension + "loop=" + loop + "cycle=" + cycle + "directionType=" + directionType + "pathInterpolation=" + pathInterpolation + "eps=" + eps + '}'; + } + + +} diff --git a/engine/src/core/com/jme3/animation/AnimationPathListener.java b/engine/src/core/com/jme3/animation/AnimationPathListener.java new file mode 100644 index 000000000..bd157110f --- /dev/null +++ b/engine/src/core/com/jme3/animation/AnimationPathListener.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.animation; + +/** + * + * @author Nehon + * @deprecated use {@link MotionPathListener} + */ +@Deprecated +public interface AnimationPathListener { + + /** + * Triggers every time the target reach a waypoint on the path + * @param path the animation path on wich the even has been triggered + * @param wayPointIndex the index of the way point reached + */ + public void onWayPointReach(AnimationPath path,int wayPointIndex); + +} 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 000000000..a08e1b242 --- /dev/null +++ b/engine/src/core/com/jme3/animation/Bone.java @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Bone 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 children = new ArrayList(); + /** + * 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 = new Vector3f(1.0f, 1.0f, 1.0f); + /** + * 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(); + + /** + * Creates a new bone with the given name. + * + * @param name Name to give to this bone + */ + public Bone(String name) { + this.name = name; + + initialPos = new Vector3f(); + initialRot = new Quaternion(); + initialScale = new Vector3f(); + + worldBindInversePos = new Vector3f(); + worldBindInverseRot = new Quaternion(); + worldBindInverseScale = new Vector3f(); + } + + /** + * Copy constructor. local bind and world inverse bind transforms shallow copied. + * @param source + */ + 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.. + } + + /** + * Used for binary loading as a Savable; the object must be constructed, + * then the parameters usually present in the constructor for this class are + * restored from the file the object was saved to. + */ + public Bone() { + } + + /** + * @return The name of the bone, set in the constructor. + */ + public String getName() { + return name; + } + + /** + * @return The parent bone of this bone, or null if it is a root bone. + */ + public Bone getParent() { + return parent; + } + + /** + * @return All the children bones of this bone. + */ + public ArrayList getChildren() { + return children; + } + + /** + * @return The local position of the bone, relative to the parent bone. + */ + public Vector3f getLocalPosition() { + return localPos; + } + + /** + * @return The local rotation of the bone, relative to the parent bone. + */ + public Quaternion getLocalRotation() { + return localRot; + } + + /** + * @return The local scale of the bone, relative to the parent bone. + */ + public Vector3f getLocalScale() { + return localScale; + } + + /** + * @return The position of the bone in model space. + */ + public Vector3f getModelSpacePosition() { + return worldPos; + } + + /** + * @return The rotation of the bone in model space. + */ + public Quaternion getModelSpaceRotation() { + return worldRot; + } + + /** + * @return The scale of the bone in model space. + */ + public Vector3f getModelSpaceScale() { + return worldScale; + } + + public Vector3f getWorldBindInversePosition() { + return worldBindInversePos; + } + + public Quaternion getWorldBindInverseRotation() { + return worldBindInverseRot; + } + + public Vector3f getWorldBindInverseScale() { + return worldBindInverseScale; + } + + /** + * 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 + */ + 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. + */ + 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(); + } + } + + //Temp 3x3 rotation matrix for transform computation + Matrix3f rotMat=new Matrix3f(); + /** + * Stores the skinning transform in the specified Matrix4f. + * The skinning transform applies the animation of the bone to a vertex. + * @param m + */ + void getOffsetTransform(Matrix4f m, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3) { + + //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 + m.loadIdentity(); + m.setTransform(translate, scale, rotate.toRotationMatrix(rotMat)); + } + + /** + * Set user transform. + * @see setUserControl + */ + 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.addLocal(scale); + } + + /** + * Must update all bones in skeleton for this to work. + * @param translation + * @param rotation + *///TODO: add scale here ??? + public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) { + if (!userControl) { + throw new IllegalStateException("User control must be on bone to allow user transforms"); + } + + worldPos.set(translation); + worldRot.set(rotation); + } + + /** + * Returns the attachment node. + * Attach models and effects to this node to make + * them follow this bone's motions. + */ + public Node getAttachmentsNode() { + if (attachNode == null) { + attachNode = new Node(name + "_attachnode"); + attachNode.setUserData("AttachedBone", this); + } + return attachNode; + } + + /** + * Used internally after model cloning. + * @param attachNode + */ + public 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.slerp(tmpQ, weight); + + //scale + if (scale != null) { + tmpV2.set(initialScale).addLocal(translation); + localScale.interpolate(tmpV2, weight); + } + + + assert vars.unlock(); + } + + /** + * 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); + if (scale != null) { + initialScale.set(scale); + } + + localPos.set(translation); + localRot.set(rotation); + if (scale != null) { + localScale.set(scale); + } + } + + void setAnimTransforms(Vector3f translation, Quaternion rotation) { + this.setAnimTransforms(translation, rotation, Vector3f.UNIT_XYZ); + } + + public void setBindTransforms(Vector3f translation, Quaternion rotation) { + this.setBindTransforms(translation, rotation, Vector3f.UNIT_XYZ); + } + + 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 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); + } + + public Vector3f getInitialPos() { + return initialPos; + } + + public Quaternion getInitialRot() { + return initialRot; + } +} 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 000000000..cbbbe71fb --- /dev/null +++ b/engine/src/core/com/jme3/animation/BoneAnimation.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 com.jme3.animation; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.BitSet; + +/** + * Bone animation updates each of it's tracks with the skeleton and time + * to apply the animation. + */ +public final class BoneAnimation implements Savable { + + private static final long serialVersionUID = 1L; + + private String name; + private float length; + + private BoneTrack[] tracks; + + public BoneAnimation(String name, float length){ + this.name = name; + this.length = length; + } + + /** + * Serialization-only. Do not use. + */ + public BoneAnimation(){ + } + + public String getName(){ + return name; + } + + public float getLength(){ + return length; + } + + public void setTracks(BoneTrack[] tracks){ + this.tracks = tracks; + } + + public BoneTrack[] getTracks(){ + return tracks; + } + + void setTime(float time, Skeleton skeleton, float weight, BitSet affectedBones){ + for (int i = 0; i < tracks.length; i++){ + if (affectedBones == null + || affectedBones.get(tracks[i].getTargetBoneIndex())) + tracks[i].setTime(time, skeleton, weight); + } + } + +// void setTime(float time, Skeleton skeleton, float weight, ArrayList affectedBones){ +// for (int i = 0; i < tracks.length; i++){ +// if (affectedBones == null +// || affectedBones.contains(tracks[i].getTargetBoneIndex())) +// tracks[i].setTime(time, skeleton, weight); +// } +// } + + public String toString(){ + return "BoneAnim[name="+name+", length="+length+"]"; + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule out = e.getCapsule(this); + out.write(name, "name", null); + out.write(length, "length", 0f); + out.write(tracks, "tracks", null); + } + + public void read(JmeImporter i) throws IOException { + InputCapsule in = i.getCapsule(this); + name = in.readString("name", null); + length = in.readFloat("length", 0f); + + Savable[] sav = in.readSavableArray("tracks", null); + if (sav != null){ + tracks = new BoneTrack[sav.length]; + System.arraycopy(sav, 0, tracks, 0, sav.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 000000000..14f2974be --- /dev/null +++ b/engine/src/core/com/jme3/animation/BoneTrack.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.animation; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * Contains a list of transforms and times for each keyframe. + */ +public final class BoneTrack implements Savable { + + /** + * 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; + // temp vectors for interpolation + private transient final Vector3f tempV = new Vector3f(); + private transient final Quaternion tempQ = new Quaternion(); + private transient final Vector3f tempS = new Vector3f(); + private transient final Vector3f tempV2 = new Vector3f(); + private transient final Quaternion tempQ2 = new Quaternion(); + private transient final Vector3f tempS2 = new Vector3f(); + + /** + * Serialization-only. Do not use. + */ + public BoneTrack() { + } + + public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations) { + this.targetBoneIndex = targetBoneIndex; + this.setKeyframes(times, translations, rotations); + } + + public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { + this(targetBoneIndex, times, translations, rotations); + this.setKeyframes(times, translations, rotations); + } + + public BoneTrack(int targetBoneIndex) { + this.targetBoneIndex = targetBoneIndex; + } + + public int getTargetBoneIndex() { + return targetBoneIndex; + } + + public Quaternion[] getRotations() { + return rotations.toObjectArray(); + } + + public Vector3f[] getScales() { + return scales == null ? null : scales.toObjectArray(); + } + + public float[] getTimes() { + return times; + } + + public Vector3f[] getTranslations() { + return translations.toObjectArray(); + } + + 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(); + } + + 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. + */ + public void setTime(float time, Skeleton skeleton, float weight) { + Bone target = skeleton.getBone(targetBoneIndex); + + 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; + } + //i is now startFrame+1; + endFrame = i ; + + 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.slerp(tempQ2, blend); + tempV.interpolate(tempV2, blend); + tempS.interpolate(tempS2, blend); + } + + if (weight != 1f) { +// tempQ.slerp(Quaternion.IDENTITY, 1f - weight); +// tempV.multLocal(weight); + target.blendAnimTransforms(tempV, tempQ, scales != null?tempS:null, weight); +// target.setAnimTransforms(tempV, tempQ); + } else { + target.setAnimTransforms(tempV, tempQ, scales != null?tempS:null); + } + } + + @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 (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(); + } + } + + + } +} 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 000000000..cf24844b5 --- /dev/null +++ b/engine/src/core/com/jme3/animation/CompactArray.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.animation; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + + +/** + * Object is indexed and stored in primitive float[] + * @author Lim, YongHoon + * @param + */ +public abstract class CompactArray { + + private Map indexPool = new HashMap(); + + protected int[] index; + protected float[] array; + private boolean invalid; + + 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); + } + + public final T get(int index, T store) { + serialize(); + int j = getCompactIndex(index); + return deserialize(j, store); + } + + public final float[] getSerializedData() { + serialize(); + return 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 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()); + } + + 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); + } + } + + 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; + } + + /** + * @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 + * @return + */ + protected abstract T deserialize(int compactIndex, T store); + + /** + * serialized size of one object element + * @return + */ + protected abstract int getTupleSize(); + + protected abstract Class 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 000000000..da4b7a1d4 --- /dev/null +++ b/engine/src/core/com/jme3/animation/CompactQuaternionArray.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.animation; + +import java.io.IOException; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Quaternion; + +/** + * Serialize and compress Quaternion[] by indexing same values + * It is converted to float[] + * @author Lim, YongHoon + */ +public class CompactQuaternionArray extends CompactArray implements Savable { + + public CompactQuaternionArray() { + } + + public CompactQuaternionArray(float[] dataArray, int[] index) { + super(dataArray, index); + } + + @Override + protected final int getTupleSize() { + return 4; + } + + @Override + protected final Class 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 000000000..af54aa73d --- /dev/null +++ b/engine/src/core/com/jme3/animation/CompactVector3Array.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.animation; + +import java.io.IOException; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; + +/** + * Serialize and compress Vector3f[] by indexing same values + * @author Lim, YongHoon + */ +public class CompactVector3Array extends CompactArray implements Savable { + + public CompactVector3Array() { + } + + public CompactVector3Array(float[] dataArray, int[] index) { + super(dataArray, index); + } + + @Override + protected final int getTupleSize() { + return 3; + } + + @Override + protected final Class 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 000000000..b74461e88 --- /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; + +/** + * LoopMode 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/MeshAnimation.java b/engine/src/core/com/jme3/animation/MeshAnimation.java new file mode 100644 index 000000000..3322d4ace --- /dev/null +++ b/engine/src/core/com/jme3/animation/MeshAnimation.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 com.jme3.animation; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.scene.Mesh; +import java.io.IOException; +import java.io.Serializable; + +public class MeshAnimation implements Serializable, Savable { + + private static final long serialVersionUID = 1L; + + private String name; + private float length; + private Track[] tracks; + + public MeshAnimation(String name, float length){ + this.name = name; + this.length = length; + } + + public String getName(){ + return name; + } + + public float getLength(){ + return length; + } + + public void setTracks(Track[] tracks){ + this.tracks = tracks; + } + + public Track[] getTracks(){ + return tracks; + } + + public void setTime(float time, Mesh[] targets, float weight){ + for (int i = 0; i < tracks.length; i++){ + tracks[i].setTime(time, targets, weight); + } + } + + + public void write(JmeExporter e) throws IOException { + OutputCapsule out = e.getCapsule(this); + out.write(name, "name", ""); + out.write(length, "length", -1f); + out.write(tracks, "tracks", null); + } + + + public void read(JmeImporter i) throws IOException { + InputCapsule in = i.getCapsule(this); + name = in.readString("name", ""); + length = in.readFloat("length", -1f); + tracks = (Track[]) in.readSavableArray("tracks", null); + } + +} diff --git a/engine/src/core/com/jme3/animation/MeshAnimationLoader.java b/engine/src/core/com/jme3/animation/MeshAnimationLoader.java new file mode 100644 index 000000000..354430d04 --- /dev/null +++ b/engine/src/core/com/jme3/animation/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.animation; + +//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 poseList, OgreMesh sharedgeom, List submeshes, Map 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> trimeshPoses = new HashMap>(); +//// +//// // find the poses for each mesh +//// for (Pose p : poses){ +//// List poseList = trimeshPoses.get(p.getTarget()); +//// if (poseList == null){ +//// poseList = new ArrayList(); +//// trimeshPoses.put(p.getTarget(), poseList); +//// } +//// +//// poseList.add(p); +//// } +//// +//// for (Map.Entry> poseEntry: trimeshPoses){ +//// PoseController +//// } +// } +// +// public static MeshAnimation loadMeshAnimation(Node animationNode, List poseList, OgreMesh sharedgeom, List submeshes){ +// String name = XMLUtil.getAttribute(animationNode, "name"); +// float length = XMLUtil.getFloatAttribute(animationNode, "length"); +// +// MeshAnimation anim = new MeshAnimation(name, length); +// List tracks = new ArrayList(); +// +// 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 loadPoses(Node posesNode, OgreMesh sharedgeom, List submeshes){ +// List poses = new ArrayList(); +// 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 offsets = new ArrayList(); +// List indices = new ArrayList(); +// +// 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 posesList){ +// List times = new ArrayList(); +// List frames = new ArrayList(); +// +// 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 poses = new ArrayList(); +// List weights = new ArrayList(); +// +// 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/core/com/jme3/animation/Pose.java b/engine/src/core/com/jme3/animation/Pose.java new file mode 100644 index 000000000..e6a4e443b --- /dev/null +++ b/engine/src/core/com/jme3/animation/Pose.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 com.jme3.animation; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.io.Serializable; +import java.nio.FloatBuffer; + +/** + * A pose is a list of offsets that say where a mesh verticles should be for this pose. + */ +public final class Pose implements Serializable, Savable { + + private static final long serialVersionUID = 1L; + + 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 vert buf, 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); + + // aquire vert + BufferUtils.populateFromBuffer(tempVec2, vertbuf, vertIndex); + + // add offset multiplied by factor + tempVec2.addLocal(tempVec); + + // write modified vert + BufferUtils.setInBuffer(tempVec2, vertbuf, vertIndex); + } + } + + 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 000000000..484a8ef3e --- /dev/null +++ b/engine/src/core/com/jme3/animation/PoseTrack.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.animation; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import java.io.IOException; +import java.io.Serializable; +import java.nio.FloatBuffer; + +/** + * A single track of pose animation associated with a certain mesh. + */ +public final class PoseTrack extends Track { + + private static final long serialVersionUID = 1L; + + private PoseFrame[] frames; + private float[] times; + + public static class PoseFrame implements Savable { + + private static final long serialVersionUID = 1L; + + Pose[] poses; + float[] weights; + + public PoseFrame(Pose[] poses, float[] weights){ + this.poses = poses; + this.weights = weights; + } + + 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){ + super(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, Mesh[] targets, float weight) { + 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); + } + } + + @Override + public void write(JmeExporter e) throws IOException { + OutputCapsule out = e.getCapsule(this); + out.write(frames, "frames", null); + out.write(times, "times", null); + } + + @Override + public void read(JmeImporter i) throws IOException { + InputCapsule in = i.getCapsule(this); + 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 000000000..add2f18be --- /dev/null +++ b/engine/src/core/com/jme3/animation/Skeleton.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Matrix4f; +import com.jme3.util.TempVars; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; + +/** + * A skeleton is a hierarchy of bones. + * Skeleton updates the world transforms to reflect the current local + * animated matrixes. + */ +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 bone is found automatically. + * @param boneList + */ + public Skeleton(Bone[] boneList) { + this.boneList = boneList; + + List rootBoneList = new ArrayList(); + 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(); + } + } + + /** + * Copy constructor. + * Most of the skeleton data is deeply-copied except the bone bind and inverseBind transforms. + * @param source + */ + 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(); + } + } + + /** + * Used for binary loading as a Savable; the object must be constructed, + * then the parameters usually present in the constructor for this class are + * restored from the file the object was saved to. + */ + 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 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(); + } + } + + public final void resetAndUpdate() { + for (int i = rootBones.length - 1; i >= 0; i--) { + Bone rootBone = rootBones[i]; + rootBone.reset(); + rootBone.update(); + } + } + + public Bone[] getRoots() { + return rootBones; + } + + public Bone getBone(int index) { + return boneList[index]; + } + + public Bone getBone(String name) { + for (int i = 0; i < boneList.length; i++) { + if (boneList[i].getName().equals(name)) { + return boneList[i]; + } + } + + return null; + } + + public int getBoneIndex(Bone bone) { + for (int i = 0; i < boneList.length; i++) { + if (boneList[i] == bone) { + return i; + } + } + + return -1; + } + + public int getBoneIndex(String name) { + for (int i = 0; i < boneList.length; i++) { + if (boneList[i].getName().equals(name)) { + return i; + } + } + + return -1; + } + + public Matrix4f[] computeSkinningMatrices() { + TempVars vars = TempVars.get(); + assert vars.lock(); + for (int i = 0; i < boneList.length; i++) { + boneList[i].getOffsetTransform(skinningMatrixes[i], vars.quat1, vars.vect1, vars.vect2); + } + assert vars.unlock(); + return skinningMatrixes; + } + + 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/Track.java b/engine/src/core/com/jme3/animation/Track.java new file mode 100644 index 000000000..a330665b6 --- /dev/null +++ b/engine/src/core/com/jme3/animation/Track.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.animation; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import com.jme3.scene.Mesh; +import java.io.IOException; +import java.io.Serializable; + +/** + * A single track of mesh animation (either morph or pose based). + * Currently morph animations are not supported (only pose). + */ +public abstract class Track implements Serializable, Savable { + + private static final long serialVersionUID = 1L; + + protected int targetMeshIndex; + + public Track(int targetMeshIndex){ + this.targetMeshIndex = targetMeshIndex; + } + + public int getTargetMeshIndex(){ + return targetMeshIndex; + } + + public abstract void setTime(float time, Mesh[] targets, float weight); + + public void write(JmeExporter ex) throws IOException{ + ex.getCapsule(this).write(targetMeshIndex, "meshIndex", 0); + } + + public void read(JmeImporter im) throws IOException{ + targetMeshIndex = im.getCapsule(this).readInt("meshIndex", 0); + } +} 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 000000000..873875647 --- /dev/null +++ b/engine/src/core/com/jme3/animation/package.html @@ -0,0 +1,78 @@ + + + + + + + + + +The com.jme3.animation 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. + +

Usage

+ +

+ +// Create or load a model with skeletal animation:
+Spatial model = assetManager.loadModel("...");
+
+// Retrieve the AnimControl.
+AnimControl animCtrl = model.getControl(AnimControl.class);
+
+// Create an animation channel, by default assigned to all bones.
+AnimChannel animChan = animCtrl.createChannel();
+
+// Play an animation
+animChan.setAnim("MyAnim");
+
+
+

Skeletal Animation System

+
+

+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 must 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 BoneWeight and BoneIndex. +The BoneIndex buffer must be of the format UnsignedByte, thus +placing the limit of up to 256 bones for a skeleton. The BoneWeight buffer +should be of the format Float. Both buffers should reference 4 +bones, even if the maximum number of bones any vertex is influenced is less or more +than 4.
+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.
+

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

+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 must 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() }.
+

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

+ + + 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 000000000..efda06c46 --- /dev/null +++ b/engine/src/core/com/jme3/app/AppTask.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.app; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AppTask is used in AppTaskQueue 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 implements Future { + private static final Logger logger = Logger.getLogger(AppTask.class + .getName()); + + private final Callable callable; + + private V result; + private ExecutionException exception; + private boolean cancelled, finished; + private final ReentrantLock stateLock = new ReentrantLock(); + private final Condition finishedCondition = stateLock.newCondition(); + + public AppTask(Callable callable) { + this.callable = callable; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + // TODO mayInterruptIfRunning was ignored in previous code, should this param be removed? + 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 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 000000000..5630bca08 --- /dev/null +++ b/engine/src/core/com/jme3/app/Application.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.app; + +import com.jme3.app.state.AppStateManager; +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.system.*; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Renderer; +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.Listener; +import com.jme3.input.InputManager; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +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 Application class represents an instance of a + * real-time 3D rendering jME application. + * + * An Application 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; + 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 InputManager inputManager; + protected AppStateManager stateManager; + + private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>(); + + /** + * Create a new instance of Application. + */ + public Application(){ + } + + public boolean isPauseOnLostFocus() { + return pauseOnFocus; + } + + public void setPauseOnLostFocus(boolean pauseOnLostFocus) { + this.pauseOnFocus = pauseOnLostFocus; + } + + 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.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. Examples of + * display parameters include display pixel width and height, + * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency. + * + * @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(); + } + } + + private void initDisplay(){ + // aquire important objects + // from the context + settings = context.getSettings(); + timer = context.getTimer(); + + renderer = context.getRenderer(); + } + + private void initAudio(){ + if (settings.getAudioRenderer() != null){ + audioRenderer = JmeSystem.newAudioRenderer(settings); + audioRenderer.initialize(); + + 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.setClearEnabled(true); + + // Create a new cam for the gui + Camera guiCam = new Camera(settings.getWidth(), settings.getHeight()); + guiViewPort = renderManager.createPostView("Gui Default", guiCam); + guiViewPort.setClearEnabled(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(); + + if (!settings.getBoolean("DisableJoysticks")){ + joyInput = context.getJoyInput(); + if (joyInput != null) + joyInput.initialize(); + } + + inputManager = new InputManager(mouseInput, keyInput, joyInput); + } + + private void initStateManager(){ + stateManager = new AppStateManager(this); + } + + /** + * @return The asset manager for this application. + */ + public AssetManager getAssetManager(){ + return assetManager; + } + + /** + * @return the input manager. + */ + public InputManager getInputManager(){ + return inputManager; + } + + /** + * @return the app state manager + */ + public AppStateManager getStateManager() { + return stateManager; + } + + /** + * @return the render manager + */ + public RenderManager getRenderManager() { + return renderManager; + } + + /** + * @return The renderer for the application, or null if was not started yet. + */ + public Renderer getRenderer(){ + return renderer; + } + + /** + * @return The audio renderer for the application, or null if was not started yet. + */ + public AudioRenderer getAudioRenderer() { + return audioRenderer; + } + + /** + * @return The listener object for audio + */ + public Listener getListener() { + return listener; + } + + /** + * @return The display context for the application, or null if was not + * started yet. + */ + public JmeContext getContext(){ + return context; + } + + /** + * @return The camera for the application, or null if was not started yet. + */ + public Camera getCamera(){ + return cam; + } + + public void start(){ + start(JmeContext.Type.Display); + } + + /** + * Starts the application. Creating a display and running the main loop. + */ + 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); + } + + 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); + } + + public void startCanvas(){ + startCanvas(false); + } + + public void startCanvas(boolean waitFor){ + context.create(waitFor); + } + + public void reshape(int w, int h){ + renderManager.notifyReshape(w, h); + } + + public void restart(){ + context.setSettings(settings); + context.restart(); + } + + public void stop(){ + stop(false); + } + + /** + * Requests the display to close, shutting down the main loop + * and making neccessary cleanup operations. + */ + public void stop(boolean waitFor){ + logger.log(Level.FINE, "Closing application: {0}", getClass().getName()); + context.destroy(waitFor); + } + + /** + * Do not call manually. + * Callback from ContextListener. + * + * Initializes the Application, 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(); + initStateManager(); + + // update timer so that the next delta is not too large +// timer.update(); + timer.reset(); + + // user code here.. + } + + public void handleError(String errMsg, Throwable t){ + logger.log(Level.SEVERE, errMsg, t); + } + + public void gainFocus(){ + if (pauseOnFocus){ + paused = false; + context.setAutoFlushFrames(true); + if (inputManager != null) + inputManager.reset(); + } + } + + public void loseFocus(){ + if (pauseOnFocus){ + paused = true; + context.setAutoFlushFrames(false); + } + } + + public void requestClose(boolean esc){ + context.destroy(false); + } + + public Future enqueue(Callable callable) { + AppTask task = new AppTask(callable); + taskQueue.add(task); + return task; + } + + /** + * Do not call manually. + * Callback from ContextListener. + */ + public void update(){ + 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(); + + inputManager = null; + } + + /** + * Do not call manually. + * Callback from ContextListener. + */ + public void destroy(){ + stateManager.cleanup(); + + destroyInput(); + if (audioRenderer != null) + audioRenderer.cleanup(); + + timer.reset(); + } + + public ViewPort getGuiViewPort() { + return guiViewPort; + } + + public ViewPort getViewPort() { + return viewPort; + } + + + +} 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 000000000..fa7648ceb --- /dev/null +++ b/engine/src/core/com/jme3/app/SimpleApplication.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.app; + +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; + +/** + * SimpleApplication 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, SimpleApplication 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 SimpleApplication:
+ * + * + * + * + * + *
Esc- Close the application
C- Display the camera position and rotation in the console.
M- Display memory usage in the console.
+ */ +public abstract class SimpleApplication extends Application { + + protected Node rootNode = new Node("Root Node"); + protected Node guiNode = new Node("Gui Node"); + protected float secondCounter = 0.0f; + protected BitmapText fpsText; + protected BitmapFont guiFont; + protected StatsView statsView; + 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("SIMPLEAPP_Exit")) { + stop(); + } else if (name.equals("SIMPLEAPP_CameraPos")) { + 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("SIMPLEAPP_Memory")) { + BufferUtils.printCurrentDirectMemory(null); + } + } + } + + public SimpleApplication() { + super(); + } + + @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; + } + + /** + * Attaches FPS statistics to guiNode and displays it on the screen. + * + */ + public void loadFPSText() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + fpsText = new BitmapText(guiFont, false); + fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); + fpsText.setText("Frames per second"); + 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", assetManager, renderer.getStatistics()); +// move it up so it appears above fps text + statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0); + guiNode.attachChild(statsView); + } + + @Override + public void initialize() { + super.initialize(); + + guiNode.setQueueBucket(Bucket.Gui); + guiNode.setCullHint(CullHint.Never); + loadFPSText(); + loadStatsView(); + viewPort.attachScene(rootNode); + guiViewPort.attachScene(guiNode); + + if (inputManager != null) { + flyCam = new FlyByCamera(cam); + flyCam.setMoveSpeed(1f); + flyCam.registerWithInput(inputManager); + + if (context.getType() == Type.Display) { + inputManager.addMapping("SIMPLEAPP_Exit", new KeyTrigger(KeyInput.KEY_ESCAPE)); + } + + inputManager.addMapping("SIMPLEAPP_CameraPos", new KeyTrigger(KeyInput.KEY_C)); + inputManager.addMapping("SIMPLEAPP_Memory", new KeyTrigger(KeyInput.KEY_M)); + inputManager.addListener(actionListener, "SIMPLEAPP_Exit", + "SIMPLEAPP_CameraPos", "SIMPLEAPP_Memory"); + } + + // 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; + + secondCounter += timer.getTimePerFrame(); + int fps = (int) timer.getFrameRate(); + if (secondCounter >= 1.0f) { + fpsText.setText("Frames per second: " + fps); + secondCounter = 0.0f; + } + + // update states + stateManager.update(tpf); + + // simple update and root node + simpleUpdate(tpf); + rootNode.updateLogicalState(tpf); + guiNode.updateLogicalState(tpf); + rootNode.updateGeometricState(); + guiNode.updateGeometricState(); + + // render states + stateManager.render(renderManager); + renderManager.render(tpf); + simpleRender(renderManager); + stateManager.postRender(); + } + + public abstract void simpleInitApp(); + + public void simpleUpdate(float tpf) { + } + + public void simpleRender(RenderManager rm) { + } +} 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 000000000..48df4a9a3 --- /dev/null +++ b/engine/src/core/com/jme3/app/StatsView.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.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 StatsView 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.
+ *
+ * Usage:
+ * 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 StatsView to the scene graph.
+ *
+ * Statistics stats = renderer.getStatistics();
+ * StatsView statsView = new StatsView("MyStats", assetManager, stats);
+ * rootNode.attachChild(statsView);
+ *
+ */ +public class StatsView extends Node implements Control { + + private BitmapText[] labels; + private Statistics statistics; + + private String[] statLabels; + private int[] statData; + + 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) { + 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); + } + statistics.clearFrame(); + } + + public Control cloneForSpatial(Spatial spatial) { + return (Control) spatial; + } + + public void setSpatial(Spatial spatial) { + } + + public void setEnabled(boolean enabled) { + } + + public boolean isEnabled() { + return true; + } + + 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 000000000..1f6e68d12 --- /dev/null +++ b/engine/src/core/com/jme3/app/package.html @@ -0,0 +1,80 @@ + + + + + + + + + +The com.jme3.application 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.
+

+An Application will typically provide the following services: +

    +
  • {@link com.jme3.asset.AssetManager} - A system for finding and loading + data assets included with the application, such as models and textures.
  • +
  • {@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.
  • +
  • {@link com.jme3.input.InputManager} - An interface for handling input + from devices such as keyboard, mouse, and gamepad/joystick.
  • +
  • {@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.
  • +
  • {@link com.jme3.audio.AudioRenderer} - Allows playing sound effects and + music.
  • +
  • {@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.
  • +
  • {@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.
  • +
+ + +

Usage

+ +An example use of the Application class is as follows
+
+ + +public class ExampleUse extends Application {
+
+ private Node rootNode = new Node("Root Node");
+
+ public static void main(String[] args){
+ ExampleUse app = new ExampleUse();
+ app.start();
+ }
+
+ @Override
+ public void initialize(){
+ super.initialize();
+
+ // attach root node to viewport
+ viewPort.attachScene(rootNode);
+ }
+
+ @Override
+ public void update(){
+ super.update();
+
+ float tpf = timer.getTimePerFrame();
+
+ // update rootNode
+ rootNode.updateLogicalState(tpf);
+ rootNode.updateGeometricState();
+
+ // render the viewports
+ renderManager.render(tpf);
+ }
+}
+
+
+ + + + 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 000000000..a64a23311 --- /dev/null +++ b/engine/src/core/com/jme3/app/state/AbstractAppState.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.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; + +/** + * AbstractAppState implements some common methods + * that make creation of AppStates easier. + * @author Kirill Vainer + */ +public class AbstractAppState implements AppState { + + /** + * initialized 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, initialized + * is set back to false. + */ + protected boolean initialized = false; + private boolean active = true; + + public void initialize(AppStateManager stateManager, Application app) { + initialized = true; + } + + public boolean isInitialized() { + return initialized; + } + + public void setActive(boolean active) { + this.active = active; + } + + public boolean isActive() { + return active; + } + + 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 000000000..c8c1f66a1 --- /dev/null +++ b/engine/src/core/com/jme3/app/state/AppState.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.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; + +/** + * AppState represents a continously executing code inside the main loop. + * An AppState can track when it is attached to the + * {@link AppStateManager} or when it is detached.
AppStates + * 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.
+ * + * + * @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 initialize() was called on the state, + * false otherwise. + */ + public boolean isInitialized(); + + /** + * Activate or deactivate the functionality of the AppState. + * The effect of this call depends on implementation. An + * AppState starts as being active by default. + * + * @param active activate the AppState or not. + */ + public void setActive(boolean active); + + /** + * @return True if the AppState is active, false otherwise. + * + * @see AppState#setActive(boolean) + */ + public boolean isActive(); + + /** + * 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 000000000..2ea5b5ad7 --- /dev/null +++ b/engine/src/core/com/jme3/app/state/AppStateManager.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.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; +import java.util.ArrayList; + +/** + * The AppStateManager holds a list of {@link AppState}s which + * it will update and render.
+ * 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. + * + * @author Kirill Vainer + */ +public class AppStateManager { + + private final ArrayList states = new ArrayList(); + private final Application app; + + public AppStateManager(Application app){ + this.app = app; + } + + /** + * 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)){ + state.stateAttached(this); + states.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); + 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); + } + } + + /** + * Returns the first state that is an instance of subclass of the specified class. + * @param + * @param stateClass + * @return First attached state that is an instance of stateClass + */ + public T getState(Class stateClass){ + synchronized (states){ + int num = states.size(); + for (int i = 0; i < num; i++){ + AppState state = states.get(i); + if (stateClass.isAssignableFrom(state.getClass())){ + return (T) state; + } + } + } + return null; + } + + /** + * Calls update for attached states, do not call directly. + * @param tpf Time per frame. + */ + public void update(float tpf){ + synchronized (states){ + int num = states.size(); + for (int i = 0; i < num; i++){ + AppState state = states.get(i); + if (!state.isInitialized()) + state.initialize(this, app); + + if (state.isActive()) { + state.update(tpf); + } + } + } + } + + /** + * Calls render for all attached states, do not call directly. + * @param rm The RenderManager + */ + public void render(RenderManager rm){ + synchronized (states){ + int num = states.size(); + for (int i = 0; i < num; i++){ + AppState state = states.get(i); + if (!state.isInitialized()) + state.initialize(this, app); + + if (state.isActive()) { + state.render(rm); + } + } + } + } + + /** + * Calls render for all attached states, do not call directly. + * @param rm The RenderManager + */ + public void postRender(){ + synchronized (states){ + int num = states.size(); + for (int i = 0; i < num; i++){ + AppState state = states.get(i); + if (!state.isInitialized()) + state.initialize(this, app); + + if (state.isActive()) { + state.postRender(); + } + } + } + } + + /** + * Calls cleanup on attached states, do not call directly. + */ + public void cleanup(){ + synchronized (states){ + for (int i = 0; i < states.size(); i++){ + AppState state = states.get(i); + state.cleanup(); + } + } + } +} 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 000000000..0af00aa4f --- /dev/null +++ b/engine/src/core/com/jme3/asset/Asset.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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. + */ +public interface Asset { + public void setKey(AssetKey key); + public AssetKey getKey(); +} 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 000000000..8b1517092 --- /dev/null +++ b/engine/src/core/com/jme3/asset/AssetConfig.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.asset; + +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.util.Scanner; + +/** + * AssetConfig loads a config file to configure the asset manager. + *

+ * The config file is specified with the following format: + * + * "LOADER" : ( ",")* + * "LOCATOR" : ( ",")* + * + * + * @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(); + } + manager.registerLoader(loaderClass, extensions); + }else if (cmd.equals("LOCATOR")){ + String rootPath = scan.next(); + String locatorClass = scan.nextLine().trim(); + manager.registerLocator(rootPath, locatorClass); + }else{ + throw new IOException("Expected command, got '"+cmd+"'"); + } + } + } + + 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 000000000..133c0e763 --- /dev/null +++ b/engine/src/core/com/jme3/asset/AssetEventListener.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.asset; + +/** + * AssetEventListener 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); + +} 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 000000000..786b9fafd --- /dev/null +++ b/engine/src/core/com/jme3/asset/AssetInfo.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; + +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}. + * @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 000000000..e906f139a --- /dev/null +++ b/engine/src/core/com/jme3/asset/AssetKey.java @@ -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. + */ + +package com.jme3.asset; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + * AssetKey is a key that is used to + * look up a resource from a cache. + * This class should be immutable. + */ +public class AssetKey implements Savable { + + protected String name; + protected transient String folder; + protected transient String extension; + + public AssetKey(String name){ + this.name = name; + this.extension = getExtension(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().indexOf(".xml")==name.length()-4){ + 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 AssetKey'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; + } + + @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 = ic.readString("name", null); + extension = getExtension(name); + } + +} 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 000000000..4ebffc54a --- /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 AssetLoader 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 000000000..c0cb831df --- /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; + +/** + * AssetLocator 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 + */ + 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 000000000..ad44c5151 --- /dev/null +++ b/engine/src/core/com/jme3/asset/AssetManager.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.asset; + +import com.jme3.audio.AudioKey; +import com.jme3.audio.AudioData; +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; + +/** + * AssetManager provides an interface for managing the data assets + * of a jME3 application. + */ +public interface AssetManager { + + /** + * 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 loaderClass, String ... extensions); + + /** + * Registers the given locator class for locating assets with this + * AssetManager. {@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. + * + * @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) + */ + public void registerLocator(String rootPath, Class locatorClass); + + /** + * Set an {@link AssetEventListener} to receive events from this + * AssetManager. There can only be one {@link AssetEventListener} + * associated with an AssetManager + * + * @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.
+ * The call will attempt to locate the asset by invoking the + * {@link AssetLocator} that are registered with this AssetManager, + * 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 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 loadAsset(AssetKey key); + + /** + * Load a named asset by name, calling this method + * is the same as calling + * + * loadAsset(new AssetKey(name)). + * + * + * @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 + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Texture loadTexture(String name); + + /** + * Load audio file, supported types are WAV or OGG. + * @param key + * @return + * + * @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 + * + * @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 + * + * @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 + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Spatial loadModel(String name); + + /** + * Load a material (J3M) file. + * @param name + * @return + * + * @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 + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public BitmapFont loadFont(String name); +} 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 000000000..42f1e0c79 --- /dev/null +++ b/engine/src/core/com/jme3/asset/ModelKey.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 com.jme3.asset; + +import com.jme3.scene.Spatial; + +/** + * + * @author Kirill Vainer + */ +public class ModelKey extends AssetKey { + + public ModelKey(String name){ + super(name); + } + + public ModelKey(){ + super(); + } + + @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 000000000..d3d778df6 --- /dev/null +++ b/engine/src/core/com/jme3/asset/TextureKey.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.asset; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureCubeMap; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class TextureKey extends AssetKey { + + private boolean generateMips; + private boolean flipY; + private boolean asCube; + private int anisotropy; + + 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 + */ + @Override + public boolean useSmartCache(){ + return true; + } + + 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{ + 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 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/audio/ALObject.java b/engine/src/core/com/jme3/audio/ALObject.java new file mode 100644 index 000000000..1076d234e --- /dev/null +++ b/engine/src/core/com/jme3/audio/ALObject.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; + +public abstract class ALObject { + + protected int id = -1; + protected Object handleRef = null; + protected boolean updateNeeded = true; + + public void setId(int id){ + if (this.id != -1) + throw new IllegalStateException("ID has already been set for this AL object."); + + this.id = id; + } + + public int getId(){ + return id; + } + + public void setUpdateNeeded(){ + updateNeeded = true; + } + + public void clearUpdateNeeded(){ + updateNeeded = false; + } + + public boolean isUpdateNeeded(){ + return updateNeeded; + } + + @Override + public String toString(){ + return getClass().getSimpleName() + " " + Integer.toHexString(hashCode()); + } + + public abstract void resetObject(); + + public abstract void deleteObject(AudioRenderer r); + +} 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 000000000..ed5dbefcd --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioBuffer.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.audio; + +import com.jme3.audio.AudioData.DataType; +import java.nio.ByteBuffer; + +/** + * An AudioBuffer is an implementation of AudioData + * where the audio is buffered (stored in memory). All parts of it + * are accessable at any time.
+ * AudioBuffers are useful for short sounds, like effects, etc. + * + * @author Kirill + */ +public class AudioBuffer extends AudioData { + + /** + * The audio data buffer. Should be direct and native ordered. + */ + protected ByteBuffer audioData; + + public AudioBuffer(){ + } + + public DataType getDataType() { + return DataType.Buffer; + } + + /** + * @return The duratiion 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) { + ar.deleteAudioData(this); + } + +} 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 000000000..33492305a --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioData.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; + +/** + * AudioData 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) is + * streamed from the hard drive as it is played. + * + * @author Kirill + */ +public abstract class AudioData extends ALObject { + + protected int sampleRate; + protected int channels; + protected int bitsPerSample; + + public enum DataType { + Buffer, + Stream + } + + /** + * @return The data type, either Buffer or Stream. + */ + 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 000000000..616b5eb68 --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioKey.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.audio; + +import com.jme3.asset.AssetKey; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * AudioKey is extending AssetKey by holding stream flag. + * + * @author Kirill + */ +public class AudioKey extends AssetKey { + + private boolean stream; + + /** + * 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 ? "/S" : ""); + } + + public boolean isStream() { + return stream; + } + + public boolean shouldCache(){ + return !stream; + } + + @Override + public void write(JmeExporter ex) throws IOException{ + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(stream, "do_stream", false); + } + + @Override + public void read(JmeImporter im) throws IOException{ + super.read(im); + InputCapsule ic = im.getCapsule(this); + stream = ic.readBoolean("do_stream", 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 000000000..c7f177d7e --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioNode.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.audio; + +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.scene.Node; +import java.io.IOException; + +/** + * + * @author normenhansen + */ +public class AudioNode extends Node { + + protected AudioRenderer renderer; + + protected boolean loop = false; + protected float volume = 1; + protected float pitch = 1; + protected float timeOffset = 0; + protected Filter dryFilter; + protected AudioKey key; + protected transient AudioData data = null; + protected transient Status status = Status.Stopped; + protected transient int channel = -1; + protected Vector3f velocity = new Vector3f(); + protected boolean reverbEnabled = true; + protected float maxDistance = 20; // 20 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; + private boolean positional = true; + + public enum Status { + Playing, + Paused, + Stopped, + } + + public AudioNode() { + } + + public AudioNode(AudioData ad, AudioKey key) { + this(); + setAudioData(ad, key); + } + + public AudioNode(AssetManager manager, String name, boolean stream) { + this(); + this.key = new AudioKey(name, stream); + this.data = (AudioData) manager.loadAsset(key); + } + + public AudioNode(AssetManager manager, String name) { + this(manager, name, false); + } + + public void setChannel(AudioRenderer renderer, int channel) { + if (status != Status.Stopped) { + throw new IllegalStateException("Can only set source id when stopped"); + } + + this.renderer = renderer; + this.channel = channel; + } + + public int getChannel() { + return channel; + } + + public Filter getDryFilter() { + return dryFilter; + } + + public void setDryFilter(Filter dryFilter) { + if (this.dryFilter != null) { + throw new IllegalStateException("Filter already set"); + } + + this.dryFilter = dryFilter; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.DryFilter); + } + + public void setAudioData(AudioData ad, AudioKey key) { + if (data != null) { + throw new IllegalStateException("Cannot change data once its set"); + } + + data = ad; + this.key = key; + } + + public AudioData getAudioData() { + return data; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public boolean isLooping() { + return loop; + } + + public void setLooping(boolean loop) { + this.loop = loop; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.Looping); + } + + public float getPitch() { + return pitch; + } + + 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 (renderer != null) + renderer.updateSourceParam(this, AudioParam.Pitch); + } + + public float getVolume() { + return volume; + } + + public void setVolume(float volume) { + if (volume < 0f) { + throw new IllegalArgumentException("Volume cannot be negative"); + } + + this.volume = volume; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.Volume); + } + + public float getTimeOffset() { + return timeOffset; + } + + public void setTimeOffset(float timeOffset) { + if (timeOffset < 0f) { + throw new IllegalArgumentException("Time offset cannot be negative"); + } + + this.timeOffset = timeOffset; + } + + public Vector3f getVelocity() { + return velocity; + } + + public void setVelocity(Vector3f velocity) { + this.velocity.set(velocity); + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.Velocity); + } + + public boolean isReverbEnabled() { + return reverbEnabled; + } + + public void setReverbEnabled(boolean reverbEnabled) { + this.reverbEnabled = reverbEnabled; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.ReverbEnabled); + } + + public Filter getReverbFilter() { + return reverbFilter; + } + + public void setReverbFilter(Filter reverbFilter) { + if (this.reverbFilter != null) { + throw new IllegalStateException("Filter already set"); + } + + this.reverbFilter = reverbFilter; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.ReverbFilter); + } + + public float getMaxDistance() { + return maxDistance; + } + + public void setMaxDistance(float maxDistance) { + if (maxDistance < 0) { + throw new IllegalArgumentException("Max distance cannot be negative"); + } + + this.maxDistance = maxDistance; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.MaxDistance); + } + + public float getRefDistance() { + return refDistance; + } + + public void setRefDistance(float refDistance) { + if (refDistance < 0) { + throw new IllegalArgumentException("Reference distance cannot be negative"); + } + + this.refDistance = refDistance; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.RefDistance); + } + + public boolean isDirectional() { + return directional; + } + + public void setDirectional(boolean directional) { + this.directional = directional; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.IsDirectional); + } + + public Vector3f getDirection() { + return direction; + } + + public void setDirection(Vector3f direction) { + this.direction = direction; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.Direction); + } + + public float getInnerAngle() { + return innerAngle; + } + + public void setInnerAngle(float innerAngle) { + this.innerAngle = innerAngle; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.InnerAngle); + } + + public float getOuterAngle() { + return outerAngle; + } + + public void setOuterAngle(float outerAngle) { + this.outerAngle = outerAngle; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.OuterAngle); + } + + public boolean isPositional() { + return positional; + } + + public void setPositional(boolean inHeadspace) { + this.positional = inHeadspace; + if (renderer != null) + renderer.updateSourceParam(this, AudioParam.IsPositional); + } + + @Override + public void updateGeometricState(){ + boolean updatePos = false; + if ((refreshFlags & RF_TRANSFORM) != 0){ + updatePos = true; + } + + super.updateGeometricState(); + + if (updatePos && renderer != null) + renderer.updateSourceParam(this, AudioParam.Position); + } + + @Deprecated + public boolean isUpdateNeeded(){ + return true; + } + + @Deprecated + public void clearUpdateNeeded(){ + } + +// @Override +// public AudioNode clone(){ +// try{ +// return (AudioNode) super.clone(); +// }catch (CloneNotSupportedException ex){ +// return null; +// } +// } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(key, "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); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + key = (AudioKey) ic.readSavable("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); + data = im.getAssetManager().loadAudio(key); + } + + 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 000000000..bf53c24a6 --- /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 000000000..468b6aed9 --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioRenderer.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.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 PointAudioSource#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 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 000000000..76fbd327c --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioStream.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +/** + * AudioStream 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{ + + protected InputStream in; + private float duration = -1f; + private boolean open = false; + private int[] ids; + + public AudioStream(){ + } + + 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"); + } + + 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(AudioRenderer r) { + r.deleteAudioData(this); + } + + /** + * @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!"); + } + } + +} 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 000000000..9a1279eed --- /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 000000000..d930d9e4f --- /dev/null +++ b/engine/src/core/com/jme3/audio/Filter.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.audio; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import java.io.IOException; + +public class Filter implements Savable { + + protected int id = -1; + protected boolean updateNeeded = true; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public void clearUpdateNeeded(){ + this.updateNeeded = false; + } + + public boolean isUpdateNeeded() { + return updateNeeded; + } + + public void write(JmeExporter ex) throws IOException { + // nothing to save + } + + public void read(JmeImporter im) throws IOException { + // nothing to read + } + +} 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 000000000..0b405b653 --- /dev/null +++ b/engine/src/core/com/jme3/audio/Listener.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.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; + } + + /** + * + * @return + * @deprecated Use {@link Listener#getVolume() } + */ + @Deprecated + public float getGain() { + return getVolume(); + } + + /** + * + * @param gain + * @deprecated Use {@link Listener#setVolume(float) } + */ + @Deprecated + public void setGain(float gain) { + setVolume(gain); + } + + 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); + } + + @Deprecated + public boolean isRefreshNeeded(){ + return true; + } + + public void clearRefreshNeeded(){ + } + +} 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 000000000..6b3b55e07 --- /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 000000000..d2b633239 --- /dev/null +++ b/engine/src/core/com/jme3/audio/LowPassFilter.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.audio; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +public class LowPassFilter extends Filter { + + protected float volume, highFreqVolume; + + public LowPassFilter(float volume, float highFreqVolume) { + setVolume(volume); + setHighFreqVolume(highFreqVolume); + } + + 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); + } + +} diff --git a/engine/src/core/com/jme3/audio/QueuedAudioRenderer.java b/engine/src/core/com/jme3/audio/QueuedAudioRenderer.java new file mode 100644 index 000000000..1e6121700 --- /dev/null +++ b/engine/src/core/com/jme3/audio/QueuedAudioRenderer.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.audio; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + +@Deprecated +public class QueuedAudioRenderer implements AudioRenderer, Runnable { + + private static final float UPDATE_RATE = 0.01f; + + private AudioRenderer wrapped; + private final Thread thread = new Thread(this, "jME3 Audio Thread"); + private final Queue commandQueue = new LinkedList(); + + public void updateListenerParam(Listener listener, ListenerParam param) { + } + + private enum CmdType { + Cleanup, + SetListenerParams, + SetEnvParams, + PlaySourceInstance, + PlaySource, + PauseSource, + StopSource, + } + + private static class Command { + + private CmdType type; + private Object[] args; + + public Command(CmdType type, Object ... args) { + this.type = type; + this.args = args; + } + + } + + public QueuedAudioRenderer(AudioRenderer toWrap){ + wrapped = toWrap; + } + + public void run(){ + wrapped.initialize(); + long updateRateNanos = (long) (UPDATE_RATE * 1000000000); + mainloop: while (true){ + long startTime = System.nanoTime(); + + // execute commands and update + synchronized (thread){ + while (commandQueue.size() > 0){ + Command cmd = commandQueue.remove(); + if (cmd.type == CmdType.Cleanup) + break mainloop; + + executeCommand(cmd); + } + } + wrapped.update(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){ + } + } + } + } + commandQueue.clear(); + wrapped.cleanup(); + } + + private void enqueueCommand(Command cmd){ + synchronized (thread){ + commandQueue.add(cmd); + } + } + + public void initialize(){ + if (!thread.isAlive()){ + thread.setDaemon(true); + thread.setPriority(Thread.NORM_PRIORITY+1); + thread.start(); + }else{ + throw new IllegalStateException("Initialize already called"); + } + } + + public void cleanup(){ + if (thread.isAlive()){ + enqueueCommand(new Command(CmdType.Cleanup, (Object)null )); + }else{ + throw new IllegalStateException("Already cleaned up or not initialized"); + } + } + + private void executeCommand(Command cmd){ + System.out.println("-> " + cmd.type + Arrays.toString(cmd.args)); + switch (cmd.type){ + case SetListenerParams: + wrapped.setListener( (Listener) cmd.args[0]); + break; + case PlaySource: + wrapped.playSource( (AudioNode) cmd.args[0] ); + break; + case PauseSource: + wrapped.pauseSource( (AudioNode) cmd.args[0] ); + break; + case StopSource: + wrapped.stopSource( (AudioNode) cmd.args[0] ); + break; + } + } + + public void updateSourceParam(AudioNode node, AudioParam param){ + + } + + public void setListener(Listener listener){ + enqueueCommand(new Command(CmdType.SetListenerParams, new Listener(listener))); + } + + public void setEnvironment(Environment env){ + enqueueCommand(new Command(CmdType.SetEnvParams, new Environment(env))); + } + + public void playSourceInstance(AudioNode src){ + enqueueCommand(new Command(CmdType.PlaySourceInstance, src)); + } + + public void playSource(AudioNode src){ + enqueueCommand(new Command(CmdType.PlaySource, src)); + } + + public void pauseSource(AudioNode src){ + enqueueCommand(new Command(CmdType.PauseSource, src)); + } + + public void stopSource(AudioNode src){ + enqueueCommand(new Command(CmdType.StopSource, src)); + } + + public void deleteAudioData(AudioData ad){ + } + + public void update(float tpf){ + // do nothing. updated in thread. + } + +} diff --git a/engine/src/core/com/jme3/audio/plugins/WAVLoader.java b/engine/src/core/com/jme3/audio/plugins/WAVLoader.java new file mode 100644 index 000000000..39bb133f9 --- /dev/null +++ b/engine/src/core/com/jme3/audio/plugins/WAVLoader.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioStream; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.audio.AudioKey; +import com.jme3.asset.TextureKey; +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.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 static final int[] index_table = + { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + private static final int[] step_table = + { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + private boolean readStream = false; + + private AudioBuffer audioBuffer; + private AudioStream audioStream; + private AudioData audioData; + private int bytesPerSec; + private int dataLength; + private float duration; + + private DataInput in; + private InputStream stream; + + private boolean adpcm = false; + private int predictor; + private int step_index; + private int step; + + 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){ + + }else if (compression == 17){ + adpcm = true; + }else{ + throw new IOException("WAV Loader only supports PCM or ADPCM 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.warning("Expected "+expectedBytesPerSec+" bytes per second, got "+bytesPerSec); + } + duration = dataLength / bytesPerSec; + + if (!adpcm){ + 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); + }else{ + if (bitsPerSample != 4) + throw new IOException("IMA ADPCM header currupt"); + + predictor = in.readShort(); + step_index = in.readByte(); // ???? + int what = in.readByte(); // skip reserved byte + step = index_table[what]; + + audioData.setupFormat(channels, 16, sampleRate); + } + } + + private int decodeNibble(int nibble){ + step = step_table[step_index]; + step_index += index_table[nibble]; + + if (step_index < 0) + step_index = 0; + else if (step_index > 88) + step_index = 88; + + boolean sign = (nibble & 8) != 0; + int delta = nibble & 7; + + int diff = (2 * delta + 1) * step; + if (sign) predictor -= diff; + else predictor += diff; + + predictor &= 0xFFFF; + + return predictor; + } + +// private ByteBuffer decodeAdpcm(int len){ +// dataLength = len * 4; // 4 bits per sample to 16 bits per sample +// } + + private void readDataChunkForBuffer(int len) throws IOException{ + dataLength = len; + ByteBuffer data = BufferUtils.createByteBuffer(dataLength); + byte[] buf = new byte[512]; + int read = 0; + while ( (read = stream.read(buf)) > 0){ + data.put(buf, 0, read); + } + data.flip(); + audioBuffer.updateData(data); + stream.close(); + } + + public Object load(AssetInfo info) throws IOException { + this.stream = info.openStream(); + in = new LittleEndien(stream); + + 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 = ((AudioKey)info.getKey()).isStream(); + + 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: + if (readStream){ + audioStream.updateData(stream, duration); + }else{ + readDataChunkForBuffer(len); + } + return audioData; + default: + int skipped = in.skipBytes(len); + if (skipped <= 0) + return null; + + break; + } + } + } +} 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 000000000..6d9894901 --- /dev/null +++ b/engine/src/core/com/jme3/bounding/BoundingBox.java @@ -0,0 +1,960 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 java.io.IOException; +import java.nio.FloatBuffer; + +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Plane; +import com.jme3.math.Ray; +import com.jme3.math.Transform; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +//import com.jme.scene.TriMesh; + +/** + * BoundingBox 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.
+ *
+ * A typical usage is to allow the class define the center and radius by calling + * either containAABB or averagePoints. A call to + * computeFramePoint in turn calls containAABB. + * + * @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 BoundingBox + * object. + */ + public BoundingBox() { + } + + /** + * Contstructor instantiates a new BoundingBox 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; + } + + /** + * computeFromPoints creates a new Bounding Box from a given + * set of points. It uses the containAABB method as default. + * + * @param points + * the points to contain. + */ + public void computeFromPoints(FloatBuffer points) { + containAABB(points); + } + + /** + * computeFromTris 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(); + assert vars.lock(); + 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; + + assert vars.unlock(); + } + + public void computeFromTris(int[] indices, Mesh mesh, int start, int end) { + if (end - start <= 0) { + return; + } + + TempVars vars = TempVars.get(); + assert vars.lock(); + 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; + + assert vars.unlock(); + } + + public static final 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; + } + + /** + * containAABB 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(); + assert vars.lock(); + 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; + } + + assert vars.unlock(); + + center.set(minX + maxX, minY + maxY, minZ + maxZ); + center.multLocal(0.5f); + + xExtent = maxX - center.x; + yExtent = maxY - center.y; + zExtent = maxZ - center.z; + } + + /** + * transform modifies the center of the box to reflect the + * change made via a rotation, translation and scale. + * + * @param rotate + * the rotation change. + * @param translate + * the translation change. + * @param scale + * the size change. + * @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(); + assert vars.lock(); + 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()); + + assert vars.unlock(); + + 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(); + assert vars.lock(); + + 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()); + + assert vars.unlock(); + + return box; + } + + /** + * whichSide 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; + } + } + + /** + * merge 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; + } + } + + /** + * mergeLocal 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; +// } + + /** + * merge 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(); + assert vars.lock(); + 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; + + assert vars.unlock(); + + return rVal; + } + + /** + * clone 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; + } + + /** + * toString returns the string representation of this object. + * The form is: "Radius: RRR.SSSS Center: ". + * + * @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 com.jme.bounding.BoundingVolume#intersects(com.jme.bounding.BoundingVolume) + */ + public boolean intersects(BoundingVolume bv) { + return bv.intersectsBoundingBox(this); + } + + /** + * determines if this bounding box intersects a given bounding sphere. + * + * @see com.jme.bounding.BoundingVolume#intersectsSphere(com.jme.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 com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.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 com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray) + */ + public boolean intersects(Ray ray) { + assert Vector3f.isValidVector(center); + + float rhs; + + TempVars vars = TempVars.get(); + assert vars.lock(); + 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) { + assert vars.unlock(); + 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) { + assert vars.unlock(); + 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) { + assert vars.unlock(); + 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) { + assert vars.unlock(); + return false; + } + + fAWxDdU[1] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Y)); + rhs = xExtent * fAWdU[2] + zExtent * fAWdU[0]; + if (fAWxDdU[1] > rhs) { + assert vars.unlock(); + return false; + } + + fAWxDdU[2] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Z)); + rhs = xExtent * fAWdU[1] + yExtent * fAWdU[0]; + if (fAWxDdU[2] > rhs) { + assert vars.unlock(); + return false; + } + + assert vars.unlock(); + return true; + } + + /** + * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray) + */ + private int collideWithRay(Ray ray, CollisionResults results) { + TempVars vars = TempVars.get(); + assert vars.lock(); + 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); + assert vars.unlock(); + + 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 http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt + * + * @param v1 + * @param v2 + * @param v3 + * @return + */ + 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 + Vector3f closest = point.subtract(center); + + // 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; + } + + return FastMath.sqrt(sqrDistance); + } + + /** + * clip 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 000000000..3d0df1372 --- /dev/null +++ b/engine/src/core/com/jme3/bounding/BoundingSphere.java @@ -0,0 +1,857 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Plane; +import com.jme3.math.Ray; +import com.jme3.math.Transform; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; + +/** + * BoundingSphere 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.
+ *
+ * A typical usage is to allow the class define the center and radius by calling + * either containAABB or averagePoints. A call to + * computeFramePoint in turn calls containAABB. + * + * @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 BoundingSphere + * object. + */ + public BoundingSphere() { + } + + /** + * Constructor instantiates a new BoundingSphere 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; + } + + /** + * getRadius returns the radius of the bounding sphere. + * + * @return the radius of the bounding sphere. + */ + public float getRadius() { + return radius; + } + + /** + * setRadius sets the radius of this bounding sphere. + * + * @param radius + * the new radius of the bounding sphere. + */ + public void setRadius(float radius) { + this.radius = radius; + } + + /** + * computeFromPoints creates a new Bounding Sphere from a + * given set of points. It uses the calcWelzl method as + * default. + * + * @param points + * the points to contain. + */ + public void computeFromPoints(FloatBuffer points) { + calcWelzl(points); + } + + /** + * computeFromTris 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); + } +// +// /** +// * computeFromTris 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 at + * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1 + * in C++ and translated to java by Cep21 + * + * @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 points. + */ + private void recurseMini(FloatBuffer points, int p, int b, int ap) { + TempVars vars = TempVars.get(); + assert vars.lock(); + Vector3f tempA = vars.vect1; + Vector3f tempB = vars.vect2; + Vector3f tempC = vars.vect3; + Vector3f tempD = 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); + assert vars.unlock(); + 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); + } + assert vars.unlock(); + recurseMini(points, i, b + 1, ap + 1); + assert vars.lock(); + } + } + assert vars.unlock(); + } + + /** + * 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); + } + + /** + * averagePoints 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; + + } + + /** + * transform modifies the center of the sphere to reflect the + * change made via a rotation, translation and scale. + * + * @param rotate + * the rotation change. + * @param translate + * the translation change. + * @param scale + * the size change. + * @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; + } + + /** + * whichSide 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; + } + } + + /** + * merge 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; + + } + } + + /** + * mergeLocal 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; + assert TempVars.get().lock(); + Vector3f radVect = TempVars.get().vect1; + radVect.set(box.xExtent, box.yExtent, box.zExtent); + Vector3f temp_center = box.center; + float len = radVect.length(); + assert TempVars.get().unlock(); + 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(); + assert vars.lock(); + 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) { + assert vars.unlock(); + return this; + } + + Vector3f rCenter = rVal.center; + if ( rCenter == null ) { + rVal.setCenter( rCenter = new Vector3f() ); + } + rCenter.set(temp_center); + rVal.setRadius(temp_radius); + assert vars.unlock(); + 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)); + assert vars.unlock(); + return rVal; + } + + /** + * clone 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)); + } + + /** + * toString returns the string representation of this object. + * The form is: "Radius: RRR.SSSS Center: ". + * + * @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(); + assert vars.lock(); + Vector3f diff = center.subtract(bs.center, vars.vect1); + float rsum = getRadius() + bs.getRadius(); + boolean eq = (diff.dot(diff) <= rsum * rsum); + assert vars.unlock(); + 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(); + assert vars.lock(); + 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); + assert vars.unlock(); + if (b >= 0.0) { + return false; + } + return b*b >= a; + } + + /* + * (non-Javadoc) + * + * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray) + */ + public int collideWithRay(Ray ray, CollisionResults results) { + TempVars vars = TempVars.get(); + assert vars.lock(); + 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); + return 1; + } + + a1 = ray.direction.dot(diff); + assert vars.unlock(); + 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{ + 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 000000000..a74bccfe2 --- /dev/null +++ b/engine/src/core/com/jme3/bounding/BoundingVolume.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.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.Matrix4f; +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.jme3.math.Plane; +import com.jme3.math.Ray; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; + +/** + * BoundingVolume 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 { + + public enum Type { + Sphere, AABB, OBB, Capsule; + } + + protected int checkPlane = 0; + 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(); + + /** + * + * transform 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); + } + + /** + * + * transform 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); + + /** + * + * whichSide 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); + + /** + * + * computeFromPoints generates a bounding volume that + * encompasses a collection of points. + * + * @param points + * the points to contain. + */ + public abstract void computeFromPoints(FloatBuffer points); + + /** + * merge 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); + + /** + * mergeLocal 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); + + /** + * clone 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 000000000..6048fc0e6 --- /dev/null +++ b/engine/src/core/com/jme3/bounding/Intersection.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.bounding; + +import com.jme3.util.TempVars; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; + +import static java.lang.Math.min; +import static java.lang.Math.max; + +/** + * 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(); + assert vars.lock(); + + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + return false; + } + + // test in Y-direction + findMinMax(tmp0.y, tmp1.y, tmp2.y, minMax); + if(minMax.x > extent.y || minMax.y < -extent.y){ + assert vars.unlock(); + return false; + } + + // test in Z-direction + findMinMax(tmp0.z, tmp1.z, tmp2.z, minMax); + if(minMax.x > extent.z || minMax.y < -extent.z){ + assert vars.unlock(); + 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){ + assert vars.unlock(); + return false; + } +// +// if(!planeBoxOverlap(normal,v0,boxhalfsize)) return false; + + assert vars.unlock(); + + 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 000000000..f383a942d --- /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
+// *
+// * +// * @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 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(); +// +// } +// +// /** +// * clip 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 000000000..2ca24ba6d --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/Cinematic.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AbstractCinematicEvent; +import com.jme3.cinematic.events.CinematicEvent; +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.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.niftygui.NiftyJmeDisplay; +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.ControlDirection; +import de.lessvoid.nifty.Nifty; +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 Savable, AppState { + + private static final Logger logger = Logger.getLogger(Application.class.getName()); + private String niftyXmlPath = null; + private Node scene; + protected TimeLine timeLine = new TimeLine(); + private int lastFetchedKeyFrame = -1; + private List cinematicEvents = new ArrayList(); + private Map cameras = new HashMap(); + private CameraNode currentCam; + private boolean initialized = false; + private Nifty nifty = null; + private Map> 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()) { + enableCurrentCam(true); + 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(); + } + } + enableCurrentCam(false); + } + + @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); + oc.write(niftyXmlPath, "niftyXmlPath", null); + + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + + cinematicEvents = ic.readSavableArrayList("cinematicEvents", null); + cameras = (Map) ic.readStringSavableMap("cameras", null); + timeLine = (TimeLine) ic.readSavable("timeLine", null); + niftyXmlPath = ic.readString("niftyXmlPath", null); + + } + + public void bindUi(String xmlPath) { + niftyXmlPath = xmlPath; + } + + @Override + public void setSpeed(float speed) { + super.setSpeed(speed); + duration = initialDuration / speed; + for (int i = 0; i < cinematicEvents.size(); i++) { + CinematicEvent ce = cinematicEvents.get(i); + ce.setSpeed(speed); + } + + + } + + public void initialize(AppStateManager stateManager, Application app) { + if (niftyXmlPath != null) { + NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(app.getAssetManager(), + app.getInputManager(), + app.getAudioRenderer(), + app.getGuiViewPort()); + nifty = niftyDisplay.getNifty(); + nifty.fromXmlWithoutStartScreen(niftyXmlPath); + app.getGuiViewPort().addProcessor(niftyDisplay); + } + for (CinematicEvent cinematicEvent : cinematicEvents) { + cinematicEvent.initEvent(app, this); + } + + + + initialized = true; + } + + public boolean isInitialized() { + return initialized; + } + + public void setActive(boolean active) { + if (active) { + play(); + } + } + + public boolean isActive() { + 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; + } + + 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() { + } + + 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(0).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(0).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(float time, final String cameraName) { + addCinematicEvent(time, new AbstractCinematicEvent() { + + @Override + public void onPlay() { + setActiveCamera(cameraName); + stop(); + } + + @Override + public void onUpdate(float tpf) { + } + + @Override + public void onStop() { + } + + @Override + public void onPause() { + } + }); + } + + public void setScene(Node scene) { + this.scene = scene; + } + + public Nifty getNifty() { + return nifty; + } + + private Map> getEventsData() { + if (eventsData == null) { + eventsData = new HashMap>(); + } + return eventsData; + } + + public void putEventData(String type, String name, Object object) { + Map> data = getEventsData(); + Map row = data.get(type); + if (row == null) { + row = new HashMap(); + } + row.put(name, object); + } + + public Object getEventData(String type, String name) { + if (eventsData != null) { + Map row = eventsData.get(type); + if (row != null) { + return row.get(name); + } + } + return null; + } + + public Savable removeEventData(String type, String name) { + if (eventsData != null) { + Map 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 000000000..1651cdefd --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/KeyFrame.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.cinematic; + +import com.jme3.cinematic.events.CinematicEvent; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Nehon + */ +public class KeyFrame implements Savable { + + List cinematicEvents = new ArrayList(); + private int index; + + public List getCinematicEvents() { + return cinematicEvents; + } + + public void setCinematicEvents(List cinematicEvents) { + this.cinematicEvents = cinematicEvents; + } + + public List 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 000000000..498afd072 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/MotionPath.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; +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.export.Savable; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Spline; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.shape.Box; +import com.jme3.math.Spline.SplineType; +import com.jme3.scene.shape.Curve; +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 listeners; + private Spline spline = new Spline(); + private float eps = 0.0001f; + //temp variables to avoid crazy instanciation + private Vector3f temp = new Vector3f(); + private Vector3f tmpVector = new Vector3f(); + + /** + * + * @deprecated replaced by com.jme3.scene.shape.Spline.SplineType + */ + @Deprecated + public enum PathInterpolation { + + /** + * Compute a linear path between the waypoints + */ + Linear, + /** + * Compute a Catmull-Rom spline path between the waypoints + * see http://www.mvps.org/directx/articles/catmull/ + */ + CatmullRom + } + + /** + * Create a motion Path + */ + public MotionPath() { + } + + /** + * interpolate the path giving the tpf and the motionControl + * @param tpf + * @param control + * @return + */ + public Vector3f interpolatePath(float tpf, MotionTrack control) { + + float val; + switch (spline.getType()) { + case CatmullRom: + + val = tpf * (spline.getTotalLength() / control.getDuration()); + control.setCurrentValue(control.getCurrentValue() + eps); + spline.interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp); + float dist = getDist(control); + + while (dist < val) { + control.setCurrentValue(control.getCurrentValue() + eps); + spline.interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp); + dist = getDist(control); + } + if (control.needsDirection()) { + tmpVector.set(temp); + control.setDirection(tmpVector.subtractLocal(control.getSpatial().getLocalTranslation()).normalizeLocal()); + } + break; + case Linear: + val = control.getDuration() * spline.getSegmentsLength().get(control.getCurrentWayPoint()) / spline.getTotalLength(); + control.setCurrentValue(Math.min(control.getCurrentValue() + tpf / val, 1.0f)); + spline.interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp); + if (control.needsDirection()) { + tmpVector.set(spline.getControlPoints().get(control.getCurrentWayPoint() + 1)); + control.setDirection(tmpVector.subtractLocal(spline.getControlPoints().get(control.getCurrentWayPoint())).normalizeLocal()); + } + break; + default: + break; + } + return temp; + } + + private float getDist(MotionTrack control) { + tmpVector.set(temp); + return tmpVector.subtractLocal(control.getSpatial().getLocalTranslation()).length(); + } + + private void attachDebugNode(Node root) { + if (debugNode == null) { + debugNode = new Node(); + Material m = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + for (Iterator 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/WireColor.j3md"); + 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/WireColor.j3md"); + 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); + + } + + /** + * 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 iterator() { + return spline.getControlPoints().iterator(); + } + + /** + * @deprecated use getPathSplineType + * return the type of path interpolation for this path + * @return the path interpolation + */ + @Deprecated + public PathInterpolation getPathInterpolation() { + if (spline.getType() == SplineType.CatmullRom) { + return PathInterpolation.CatmullRom; + } + return PathInterpolation.Linear; + } + + /** + * @deprecated use setPathSplineType instead + * sets the path interpolation for this path + * @param pathInterpolation + */ + @Deprecated + public void setPathInterpolation(PathInterpolation pathInterpolation) { + if (pathInterpolation == PathInterpolation.CatmullRom) { + setPathSplineType(SplineType.CatmullRom); + } else { + setPathSplineType(SplineType.Linear); + } + } + + /** + * 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(); + } + 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 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); + } + } + + /** + * 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(); + } +} 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 000000000..99a3dcb98 --- /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 000000000..648dc3dba --- /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 000000000..d0b5ede71 --- /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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 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 Collection 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 000000000..e6b1cadeb --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.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.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 com.jme3.export.Savable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Nehon + */ +public abstract class AbstractCinematicEvent implements CinematicEvent, Savable { + + protected PlayState playState = PlayState.Stopped; + protected float speed = 1; + protected float initialDuration = 10; + protected float duration = initialDuration / speed; + protected LoopMode loopMode = LoopMode.DontLoop; + protected float time = 0; + protected List listeners; + + public AbstractCinematicEvent() { + } + + public AbstractCinematicEvent(float initialDuration) { + this.initialDuration = initialDuration; + duration = initialDuration / speed; + } + + public AbstractCinematicEvent(LoopMode loopMode) { + this.loopMode = loopMode; + } + + public AbstractCinematicEvent(float initialDuration, LoopMode loopMode) { + this.initialDuration = initialDuration; + this.loopMode = loopMode; + duration = initialDuration / speed; + } + + 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); + } + } + } + + public abstract void onPlay(); + + public void internalUpdate(float tpf) { + if (playState == PlayState.Playing) { + time += tpf * speed; + + onUpdate(tpf); + if (time >= duration && loopMode == loopMode.DontLoop) { + stop(); + } + } + + } + + public 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); + } + } + } + + public abstract void onStop(); + + 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); + } + } + } + + public abstract void onPause(); + + /** + * returns the actual duration of the animtion (initialDuration/speed) + * @return + */ + public float getDuration() { + return duration; + } + + /** + * 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; + duration = initialDuration / 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; + duration = initialDuration / speed; + } + + /** + * 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; + } + + public void setInitalDuration(float initalDuration) { + this.initialDuration = initalDuration; + duration = initalDuration / speed; + } + + public float getInitalDuration() { + return initialDuration; + } + + 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); + } + + 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); + duration = initialDuration / speed; + loopMode = ic.readEnum("loopMode", LoopMode.class, LoopMode.DontLoop); + } + + public void initEvent(Application app, Cinematic cinematic) { + } + + private List getListeners() { + if (listeners == null) { + listeners = new ArrayList(); + } + return listeners; + } + + public void addListener(CinematicEventListener listener) { + getListeners().add(listener); + } + + public void removeListener(CinematicEventListener listener) { + getListeners().remove(listener); + } +} 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 000000000..3ba3f6d9a --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/AnimationTrack.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.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; + } + + 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); + 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) { + 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 onPlay() { + channel.getControl().setEnabled(true); + if (playState == PlayState.Stopped) { + channel.setAnim(animationName); + } + } + + @Override + public void onUpdate(float tpf) { + } + + @Override + public void onStop() { + channel.getControl().setEnabled(false); + } + + @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 000000000..cb4abeac1 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.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.events; + +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.PlayState; + +/** + * + * @author Nehon + */ +public interface CinematicEvent { + + /** + * 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 + */ + 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 + */ + public float getSpeed(); + + /** + * returns the PlayState of the animation + * @return + */ + public PlayState getPlayState(); + + /** + * @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 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 + */ + public float getInitialDuration(); + + /** + * Sets the duration of the antionamtion at speed = 1 in seconds + * @param initialDuration + */ + public void setInitialDuration(float initialDuration); + + public void internalUpdate(float tpf); + + public void initEvent(Application app, Cinematic cinematic); + +} 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 000000000..9a5f5a6e0 --- /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/GuiTrack.java b/engine/src/core/com/jme3/cinematic/events/GuiTrack.java new file mode 100644 index 000000000..d9b0daff5 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/GuiTrack.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.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(String screen) { + this.screen = screen; + } + + public GuiTrack(String screen, float initialDuration) { + super(initialDuration); + this.screen = screen; + + } + + public GuiTrack(String screen, LoopMode loopMode) { + super(loopMode); + this.screen = screen; + } + + public GuiTrack(String screen, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.screen = screen; + } + + @Override + public void initEvent(Application app, Cinematic cinematic) { + nifty = cinematic.getNifty(); + } + + @Override + public void onPlay() { + 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/core/com/jme3/cinematic/events/MotionTrack.java b/engine/src/core/com/jme3/cinematic/events/MotionTrack.java new file mode 100644 index 000000000..bc196be44 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/MotionTrack.java @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.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 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; + + + /** + * 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 += tpf * speed; + onUpdate(tpf); + if (time >= duration && loopMode == loopMode.DontLoop) { + stop(); + } + } + } + + } + + @Override + public void initEvent(Application app, Cinematic cinematic) { + isControl=false; + } + + + + public void onUpdate(float tpf) { + spatial.setLocalTranslation(path.interpolatePath(tpf, this)); + computeTargetDirection(); + + if (currentValue >= 1.0f) { + currentValue = 0; + currentWayPoint++; + path.triggerWayPointReach(currentWayPoint, this); + } + if (currentWayPoint == path.getNbWayPoints() - 1) { + if (loopMode == LoopMode.Loop) { + currentWayPoint = 0; + } else { + stop(); + } + } + } + + @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.duration = duration; + control.initialDuration = initialDuration; + control.speed = speed; + control.duration = duration; + control.loopMode = loopMode; + control.directionType = directionType; + + return control; + } + + @Override + public void onPlay() { + } + + @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) { + this.currentWayPoint = currentWayPoint; + } + + /** + * 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) { + play(); + } + + 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; + } +} 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 000000000..d085038ea --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/PositionTrack.java @@ -0,0 +1,120 @@ +/* + * 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 + */ +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 (duration == 0 && spatial != null) { + + spatial.setLocalTranslation(endPosition); + } + } + + @Override + public void onUpdate(float tpf) { + if (spatial != null) { + value += Math.min(tpf * speed / duration, 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 000000000..065add4a2 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/RotationTrack.java @@ -0,0 +1,123 @@ +/* + * 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.Quaternion; +import com.jme3.scene.Spatial; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Nehon + */ +public class RotationTrack extends AbstractCinematicEvent { + + private static final Logger log = Logger.getLogger(RotationTrack.class.getName()); + private float[] startRotation; + private float[] endRotation; + 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, float[] endRotation) { + this.endRotation = endRotation; + this.spatial = spatial; + spatialName = spatial.getName(); + } + + public RotationTrack(Spatial spatial, float[] endRotation, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.endRotation = endRotation; + this.spatial = spatial; + spatialName = spatial.getName(); + } + + public RotationTrack(Spatial spatial, float[] endRotation, LoopMode loopMode) { + super(loopMode); + this.endRotation = endRotation; + this.spatial = spatial; + spatialName = spatial.getName(); + } + + public RotationTrack(Spatial spatial, float[] endRotation, float initialDuration) { + super(initialDuration); + this.endRotation = endRotation; + this.spatial = spatial; + spatialName = spatial.getName(); + } + + @Override + public void onPlay() { + if (playState != playState.Paused) { + startRotation = spatial.getWorldRotation().toAngles(null); + } + if (duration == 0 && spatial != null) { + spatial.setLocalRotation(new Quaternion().fromAngles(endRotation)); + stop(); + } + } + + @Override + public void onUpdate(float tpf) { + if (spatial != null) { + value += Math.min(tpf * speed / duration, 1.0f); + float[] rot = new float[3]; + rot[0] = FastMath.interpolateLinear(value, startRotation[0], endRotation[0]); + rot[1] = FastMath.interpolateLinear(value, startRotation[1], endRotation[1]); + rot[2] = FastMath.interpolateLinear(value, startRotation[2], endRotation[2]); + spatial.setLocalRotation(new Quaternion().fromAngles(rot)); + } + } + + @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 = ic.readFloatArray("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 000000000..923f87f88 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java @@ -0,0 +1,119 @@ +/* + * 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 + */ +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 (duration == 0 && spatial != null) { + spatial.setLocalScale(endScale); + stop(); + } + } + + @Override + public void onUpdate(float tpf) { + if (spatial != null) { + value += Math.min(tpf * speed / duration, 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 000000000..61af58367 --- /dev/null +++ b/engine/src/core/com/jme3/cinematic/events/SoundTrack.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.LoopMode; +import com.jme3.app.Application; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioRenderer; +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 AudioRenderer audioRenderer; + 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) { + audioRenderer = app.getAudioRenderer(); + audioNode = new AudioNode(app.getAssetManager(), path, stream); + setLoopMode(loopMode); + + } + + @Override + public void onPlay() { + audioRenderer.playSource(audioNode); + } + + @Override + public void onStop() { + audioRenderer.stopSource(audioNode); + } + + @Override + public void onPause() { + audioRenderer.pauseSource(audioNode); + } + + @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 000000000..5dd823067 --- /dev/null +++ b/engine/src/core/com/jme3/collision/Collidable.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; + +/** + * Interface for collidable objects. + * @author Kirill + */ +public interface Collidable { + + /** + * Check collision with another collidable + * @param other + * @param results + * @return how many collisions were found + */ + 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 000000000..85d41c152 --- /dev/null +++ b/engine/src/core/com/jme3/collision/CollisionResult.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 com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; + +/** + * @author Kirill + */ +public class CollisionResult implements Comparable { + + 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; + } + + public Vector3f getContactPoint() { + return contactPoint; + } + + /** + * + * @return + * @deprecated Use getContactPoint() instead, its already in world space. + */ + @Deprecated + public Vector3f getWorldContactPoint() { + 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 000000000..84585934f --- /dev/null +++ b/engine/src/core/com/jme3/collision/CollisionResults.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 com.jme3.collision; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; + +public class CollisionResults implements Iterable { + + private final ArrayList results = new ArrayList(); + private boolean sorted = true; + + public void clear(){ + results.clear(); + } + + public Iterator 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 000000000..7703e5abc --- /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 000000000..f523f8df0 --- /dev/null +++ b/engine/src/core/com/jme3/collision/SweepSphere.java @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AbstractTriangle; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; + +/** + * Sweep sphere implements a collidable moving sphere. + * Usually used to simulate simple physics for character entities in games. + * The sweep sphere can be used to check collision against + * a triangle or another sweep sphere. + * + * @author Kirill Vainer + */ +@Deprecated +public 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 000000000..68192a1be --- /dev/null +++ b/engine/src/core/com/jme3/collision/UnsupportedCollisionException.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 com.jme3.collision; + +/** + * + * @author Kirill + */ +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 000000000..d425a7b35 --- /dev/null +++ b/engine/src/core/com/jme3/collision/bih/BIHNode.java @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 java.util.ArrayList; + +import static java.lang.Math.min; +import static java.lang.Math.max; + +/** + * 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; + + 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){ + + ArrayList stack = TempVars.get().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; + } + } + } + + return cols; + } + + public final int intersectBrute(Ray r, + Matrix4f worldMatrix, + BIHTree tree, + float sceneMin, + float sceneMax, + CollisionResults results){ + float tHit = Float.POSITIVE_INFINITY; + + Vector3f v1 = new Vector3f(), + v2 = new Vector3f(), + v3 = new Vector3f(); + + int cols = 0; + + ArrayList stack = TempVars.get().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 ++; + } + } + } + + return cols; + } + + public final int intersectWhere(Ray r, + Matrix4f worldMatrix, + BIHTree tree, + float sceneMin, + float sceneMax, + CollisionResults results){ + + ArrayList stack = TempVars.get().bihStack; + stack.clear(); + +// float tHit = Float.POSITIVE_INFINITY; + + Vector3f o = r.getOrigin().clone(); + Vector3f d = r.getDirection().clone(); + + Matrix4f inv = worldMatrix.invert(); + + 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 = new Vector3f(), + v2 = new Vector3f(), + v3 = new Vector3f(); + 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 ++; + } + } + } + + 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 000000000..cdf301d4a --- /dev/null +++ b/engine/src/core/com/jme3/collision/bih/BIHTree.java @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.scene.mesh.VirtualIndexBuffer; +import com.jme3.scene.mesh.WrappedIndexBuffer; +import com.jme3.bounding.BoundingSphere; +import com.jme3.scene.Mesh.Mode; +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.SweepSphere; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; + +import static java.lang.Math.max; + +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[3]; + + static { + comparators[0] = new TriangleAxisComparator(0); + comparators[1] = new TriangleAxisComparator(1); + comparators[2] = 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(); + assert vars.lock(); + 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); + assert vars.unlock(); + 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(); + assert vars.lock(); + 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; + } + } + + assert vars.unlock(); + 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()); + } + +// return root.intersectBrute(r, worldMatrix, this, tMin, tMax, results); + return root.intersectWhere(r, worldMatrix, this, tMin, tMax, results); + } + return 0; + } + + private int collideWithSweepSphere(SweepSphere ss, + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results){ + + Vector3f min = new Vector3f(ss.getCenter()); + min.subtractLocal(ss.getDimension()); + + Vector3f max = new Vector3f(ss.getCenter()); + max.addLocal(ss.getVelocity()).addLocal(ss.getDimension()); + + BoundingBox bbox = new BoundingBox(); + bbox.setMinMax(min, max); + + if (worldBound.intersectsBoundingBox(bbox)){ + return root.intersectWhere(ss, bbox, worldMatrix, this, 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 SweepSphere){ + SweepSphere ss = (SweepSphere) other; + return collideWithSweepSphere(ss, worldMatrix, worldBound, results); + }else 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 000000000..2229da07e --- /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 000000000..895dd5ae7 --- /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 { + + 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/EmitterBoxShape.java b/engine/src/core/com/jme3/effect/EmitterBoxShape.java new file mode 100644 index 000000000..ae80e2079 --- /dev/null +++ b/engine/src/core/com/jme3/effect/EmitterBoxShape.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.effect; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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); + } + + 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(); + } + + 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; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(min, "min", null); + oc.write(len, "length", null); + } + 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/EmitterPointShape.java b/engine/src/core/com/jme3/effect/EmitterPointShape.java new file mode 100644 index 000000000..942bc405e --- /dev/null +++ b/engine/src/core/com/jme3/effect/EmitterPointShape.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.effect; + +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; + } + + public EmitterShape deepClone(){ + try { + EmitterPointShape clone = (EmitterPointShape) super.clone(); + clone.point = point.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public void getRandomPoint(Vector3f store) { + store.set(point); + } + + public Vector3f getPoint() { + return point; + } + + public void setPoint(Vector3f point) { + this.point = point; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(point, "point", null); + } + + 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/EmitterShape.java b/engine/src/core/com/jme3/effect/EmitterShape.java new file mode 100644 index 000000000..49d47146a --- /dev/null +++ b/engine/src/core/com/jme3/effect/EmitterShape.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.export.Savable; +import com.jme3.math.Vector3f; + +public interface EmitterShape extends Savable, Cloneable { + public void getRandomPoint(Vector3f store); + public EmitterShape deepClone(); +} diff --git a/engine/src/core/com/jme3/effect/EmitterSphereShape.java b/engine/src/core/com/jme3/effect/EmitterSphereShape.java new file mode 100644 index 000000000..235905e69 --- /dev/null +++ b/engine/src/core/com/jme3/effect/EmitterSphereShape.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.effect; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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(); + } + } + + 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); + } + + 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; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(center, "center", null); + oc.write(radius, "radius", 0); + } + + 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/effect/Particle.java b/engine/src/core/com/jme3/effect/Particle.java new file mode 100644 index 000000000..4865dad79 --- /dev/null +++ b/engine/src/core/com/jme3/effect/Particle.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.effect; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +public class Particle { + public final Vector3f velocity = new Vector3f(); + public final Vector3f position = new Vector3f(); + public final ColorRGBA color = new ColorRGBA(0,0,0,0); + public float size = 0f; + public float life; + public float startlife; + public float angle; + public float rotateSpeed; + public int imageIndex = 0; + 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 000000000..ef8c7ab7e --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleComparator.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.effect; + +import com.jme3.renderer.Camera; +import java.util.Comparator; + +class ParticleComparator implements Comparator { + + private Camera cam; + + public void setCamera(Camera cam){ + this.cam = cam; + } + + public int compare(Particle p1, Particle p2) { + 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 000000000..561e218ba --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleEmitter.java @@ -0,0 +1,815 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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; + +public class ParticleEmitter extends Geometry implements Control { + + private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO); + + private EmitterShape shape = DEFAULT_SHAPE; + private ParticleMesh particleMesh; + private ParticleMesh.Type meshType; + private Particle[] particles; + + private int firstUnUsed; + private int lastUsed; + +// private int next = 0; +// private ArrayList unusedIndices = new ArrayList(); + + private boolean randomAngle = false; + private boolean selectRandomImage = false; + private boolean facingVelocity = false; + private float particlesPerSec = 20; + private float emitCarry = 0f; + private float lowLife = 3f; + private float highLife = 7f; + private float gravity = 0.1f; + private float variation = 0.2f; + private float rotateSpeed = 0; + private Vector3f startVel = new Vector3f(); + private Vector3f faceNormal = new Vector3f(Vector3f.NAN); + + private int imagesX = 1; + private int imagesY = 1; + + private boolean enabled = true; + 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; + + private Vector3f temp = new Vector3f(); + + @Override + public ParticleEmitter clone(){ + ParticleEmitter clone = (ParticleEmitter) super.clone(); + clone.shape = shape.deepClone(); + clone.setNumParticles(particles.length); + clone.startVel = startVel.clone(); + clone.faceNormal = faceNormal.clone(); + clone.startColor = startColor.clone(); + clone.endColor = endColor.clone(); + clone.controls.add(clone); + return clone; + } + + public ParticleEmitter(String name, Type type, int numParticles){ + super(name); + + // ignore world transform, unless user sets inLocalSpace + setIgnoreTransform(true); + + // particles neither receive nor cast shadows + setShadowMode(ShadowMode.Off); + + // particles are usually transparent + setQueueBucket(Bucket.Transparent); + + meshType = type; + + setNumParticles(numParticles); + + controls.add(this); + + switch (meshType){ + case Point: + particleMesh = new ParticlePointMesh(); + setMesh(particleMesh); + break; + case Triangle: + particleMesh = new ParticleTriMesh(); + setMesh(particleMesh); + break; + default: + throw new IllegalStateException("Unrecognized particle type: "+meshType); + } + particleMesh.initParticleData(this, particles.length); + } + + public ParticleEmitter(){ + super(); + } + + public Control cloneForSpatial(Spatial spatial){ + return (Control) spatial; + } + + public void setShape(EmitterShape shape) { + this.shape = shape; + } + + public EmitterShape getShape(){ + return shape; + } + + public boolean isInWorldSpace() { + return worldSpace; + } + + public void setInWorldSpace(boolean worldSpace) { + setIgnoreTransform(worldSpace); + this.worldSpace = worldSpace; + } + + public int getNumVisibleParticles(){ +// return unusedIndices.size() + next; + return lastUsed + 1; + } + + /** + * @param numParticles The maximum amount of particles that + * can exist at the same time with this emitter. + * Calling this method many times is not recommended. + */ + public final void setNumParticles(int numParticles){ + particles = new Particle[numParticles]; + for (int i = 0; i < numParticles; i++){ + particles[i] = new Particle(); + } + firstUnUsed = 0; + lastUsed = -1; + } + + /** + * @return A list of all particles (shouldn't be used in most cases). + * This includes both existing and non-existing particles. + * The size of the array is set to the numParticles value + * specified in the constructor or {@link ParticleEmitter#setNumParticles(int) } + * method. + */ + public Particle[] getParticles(){ + return particles; + } + + public Vector3f getFaceNormal() { + if (Vector3f.isValidVector(faceNormal)) + return faceNormal; + else + return null; + } + + /** + * Sets the normal which particles are facing. 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 null as the argument for + * faceNormal. + * + * @param faceNormal The normals particles should face, or null + * 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; + } + + public float getRotateSpeed() { + return rotateSpeed; + } + + /** + * @param rotateSpeed Set the rotation speed in radians/sec for particles + * spawned after the invocation of this method. + */ + public void setRotateSpeed(float rotateSpeed) { + this.rotateSpeed = rotateSpeed; + } + + public boolean isRandomAngle() { + return randomAngle; + } + + /** + * @param randomAngle Set to true if every particle spawned + * should have a random facing angle. + */ + public void setRandomAngle(boolean randomAngle) { + this.randomAngle = randomAngle; + } + + public boolean isSelectRandomImage() { + return selectRandomImage; + } + + /** + * @param 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. 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. + */ + public void setSelectRandomImage(boolean selectRandomImage) { + this.selectRandomImage = selectRandomImage; + } + + public boolean isFacingVelocity() { + return facingVelocity; + } + + /** + * @param followVelocity Set to true if particles spawned should face + * their velocity (or direction to which they are moving towards). + * This is typically used for e.g spark effects. + */ + public void setFacingVelocity(boolean followVelocity) { + this.facingVelocity = followVelocity; + } + + public ColorRGBA getEndColor() { + return endColor; + } + + /** + * @param endColor Set the end color of the particles spawned. 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. + */ + public void setEndColor(ColorRGBA endColor) { + this.endColor.set(endColor); + } + + public float getEndSize() { + return endSize; + } + + /** + * @param endSize Set the end size of the particles spawned.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. + */ + public void setEndSize(float endSize) { + this.endSize = endSize; + } + + public float getGravity() { + return gravity; + } + + /** + * @param gravity Set the gravity, in units/sec/sec, of particles + * spawned. + */ + public void setGravity(float gravity) { + this.gravity = gravity; + } + + public float getHighLife() { + return highLife; + } + + /** + * @param highLife Set the high value of life. The particle's lifetime/expiration + * is determined by randomly selecting a time between low life and high life. + */ + public void setHighLife(float highLife) { + this.highLife = highLife; + } + + public int getImagesX() { + return imagesX; + } + + /** + * @param imagesX Set the number of images along the X axis (width). To determine + * how multiple particle images are selected and used, see the + * {@link ParticleEmitter#setSelectRandomImage(boolean) } method. + */ + public void setImagesX(int imagesX) { + this.imagesX = imagesX; + particleMesh.setImagesXY(this.imagesX, this.imagesY); + } + + public int getImagesY() { + return imagesY; + } + + /** + * @param imagesY Set the number of images along the Y axis (height). To determine + * how multiple particle images are selected and used, see the + * {@link ParticleEmitter#setSelectRandomImage(boolean) } method. + */ + public void setImagesY(int imagesY) { + this.imagesY = imagesY; + particleMesh.setImagesXY(this.imagesX, this.imagesY); + } + + public float getLowLife() { + return lowLife; + } + + /** + * @param lowLife Set the low value of life. The particle's lifetime/expiration + * is determined by randomly selecting a time between low life and high life. + */ + public void setLowLife(float lowLife) { + this.lowLife = lowLife; + } + + public float getParticlesPerSec() { + return particlesPerSec; + } + + /** + * @param particlesPerSec Set the number of particles to spawn per + * second. + */ + public void setParticlesPerSec(float particlesPerSec) { + this.particlesPerSec = particlesPerSec; + } + + public ColorRGBA getStartColor() { + return startColor; + } + + /** + * @param startColor Set the start color of the particles spawned. 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. + */ + public void setStartColor(ColorRGBA startColor) { + this.startColor.set(startColor); + } + + public float getStartSize() { + return startSize; + } + + /** + * @param startSize Set the start size of the particles spawned.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. + */ + public void setStartSize(float startSize) { + this.startSize = startSize; + } + + /** + * @deprecated Use {@link ParticleEmitter#getInitialVelocity() } + */ + @Deprecated + public Vector3f getStartVel() { + return startVel; + } + + /** + * @deprecated Use {@link ParticleEmitter#setInitialVelocity(com.jme3.math.Vector3f) } + */ + @Deprecated + public void setStartVel(Vector3f startVel) { + this.startVel.set(startVel); + } + + public Vector3f getInitialVelocity(){ + return startVel; + } + + /** + * @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. + * + * @see ParticleEmitter#setVelocityVariation(float) + * @see ParticleEmitter#setGravity(float) + */ + public void setInitialVelocity(Vector3f initialVelocity){ + this.startVel.set(initialVelocity); + } + + /** + * @deprecated Use {@link ParticleEmitter#getVelocityVariation() } + */ + @Deprecated + public float getVariation() { + return variation; + } + + /** + * @deprecated Use {@link ParticleEmitter#setVelocityVariation() } + */ + @Deprecated + public void setVariation(float variation) { + this.variation = variation; + } + + public float getVelocityVariation() { + return variation; + } + + /** + * @param variation Set the variation by which the initial velocity + * of the particle is determined. variation 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. + */ + public void setVelocityVariation(float variation) { + this.variation = variation; + } + +// private int newIndex(){ +// liveParticles ++; +// return unusedIndices.remove(0); +// if (unusedIndices.size() > 0){ +// liveParticles++; +// return unusedIndices.remove(0); +// }else if (next < particles.length){ +// liveParticles++; +// return next++; +// }else{ +// return -1; +// } +// } + +// private void freeIndex(int index){ +// liveParticles--; +// if (index == next-1) +// next--; +// else +// assert !unusedIndices.contains(index); +// unusedIndices.add(index); +// } + + private boolean emitParticle(Vector3f min, Vector3f max){ +// int idx = newIndex(); +// if (idx == -1) +// return false; + int idx = lastUsed + 1; + if (idx >= particles.length) { + return false; + } + + 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); + if (worldSpace){ + p.position.addLocal(worldTransform.getTranslation()); + } + p.velocity.set(startVel); + if (randomAngle) + p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI; + if (rotateSpeed != 0) + p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f); + + // NOTE: Using temp variable here + temp.set(FastMath.nextRandomFloat(),FastMath.nextRandomFloat(),FastMath.nextRandomFloat()); + temp.multLocal(2f); + temp.subtractLocal(1f,1f,1f); + temp.multLocal(startVel.length()); + p.velocity.interpolate(temp, variation); + + 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 true; + } + + /** + * Instantly emits all the particles possible to be emitted. Any particles + * which are currently inactive will be spawned immediately. + */ + @SuppressWarnings("empty-statement") + public void emitAllParticles(){ + // Force world transform to update + getWorldTransform(); + + TempVars vars = TempVars.get(); + assert vars.lock(); + + BoundingBox bbox = (BoundingBox) 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)); + + bbox.setMinMax(min, max); + setBoundRefresh(); + + assert vars.unlock(); + } + + /** + * 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) + freeParticle(i); + } + } + + 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; + +// freeIndex(idx); + + 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 updateParticleState(float tpf){ + // Force world transform to update + getWorldTransform(); + + TempVars vars = TempVars.get(); + assert vars.lock(); + + 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){ + freeParticle(i); + continue; + } + + // position += velocity * tpf + p.distToCam = -1; + float g = gravity * tpf; + p.velocity.y -= g; + + // NOTE: Using temp variable + temp.set(p.velocity).multLocal(tpf); + p.position.addLocal(temp); + + 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) // use animated effect + p.imageIndex = (int) (b * imagesX * imagesY); + + if (firstUnUsed < i) { + swap(firstUnUsed, i); + if (i == lastUsed) { + lastUsed = firstUnUsed; + } + firstUnUsed++; + } + } + + float particlesToEmitF = particlesPerSec * tpf; + int particlesToEmit = (int) (particlesToEmitF); + emitCarry += particlesToEmitF - particlesToEmit; + + while (emitCarry > 1f){ + particlesToEmit ++; + emitCarry -= 1f; + } + + for (int i = 0; i < particlesToEmit; i++){ + emitParticle(min, max); + } + + BoundingBox bbox = (BoundingBox) getMesh().getBound(); + bbox.setMinMax(min, max); + setBoundRefresh(); + + assert vars.unlock(); + } + + /** + * Do not use. + */ + public void setSpatial(Spatial spatial) { + } + + /** + * @param enabled Set to enable or disable a particle. When a particle is + * disabled, it will be "frozen in time" and not update. + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public void update(float tpf) { + if (!enabled) + return; + + updateParticleState(tpf); + } + + public void render(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 + getMaterial().setFloat("Quadratic", C); + } + + Matrix3f inverseRotation = Matrix3f.IDENTITY; + if (!worldSpace){ + TempVars vars = TempVars.get(); + assert vars.lock(); + inverseRotation = getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal(); + } + particleMesh.updateParticleData(particles, cam, inverseRotation); + if (!worldSpace){ + assert TempVars.get().unlock(); + } + } + + public void preload(RenderManager rm, ViewPort vp){ + 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", 0); + oc.write(variation, "variation", 0); + oc.write(imagesX, "imagesX", 1); + oc.write(imagesY, "imagesY", 1); + + oc.write(startVel, "startVel", null); + 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(selectRandomImage, "selectRandomImage", false); + oc.write(randomAngle, "randomAngle", false); + oc.write(rotateSpeed, "rotateSpeed", 0); + } + + @Override + public void read(JmeImporter im) throws IOException{ + super.read(im); + InputCapsule ic = im.getCapsule(this); + shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE); + meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle); + int numParticles = ic.readInt("numParticles", 0); + setNumParticles(numParticles); + + enabled = ic.readBoolean("enabled", true); + particlesPerSec = ic.readFloat("particlesPerSec", 0); + lowLife = ic.readFloat("lowLife", 0); + highLife = ic.readFloat("highLife", 0); + gravity = ic.readFloat("gravity", 0); + variation = ic.readFloat("variation", 0); + imagesX = ic.readInt("imagesX", 1); + imagesY = ic.readInt("imagesY", 1); + + startVel = (Vector3f) ic.readSavable("startVel", null); + 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); + facingVelocity = ic.readBoolean("facingVelocity", false); + selectRandomImage = ic.readBoolean("selectRandomImage", false); + randomAngle = ic.readBoolean("randomAngle", false); + rotateSpeed = ic.readFloat("rotateSpeed", 0); + + switch (meshType){ + case Point: + particleMesh = new ParticlePointMesh(); + setMesh(particleMesh); + break; + case Triangle: + particleMesh = new ParticleTriMesh(); + setMesh(particleMesh); + break; + default: + throw new IllegalStateException("Unrecognized particle type: "+meshType); + } + particleMesh.initParticleData(this, particles.length); + } + +} 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 000000000..ac94b56f3 --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleMesh.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.effect; + +import com.jme3.math.Matrix3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Mesh; + +public abstract class ParticleMesh extends Mesh { + + public static enum Type { + Point, + Triangle; + } + + public abstract void initParticleData(ParticleEmitter emitter, int numParticles); + public abstract void setImagesXY(int imagesX, int imagesY); + 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 000000000..6a66fe809 --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticlePointMesh.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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); + 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); + setBuffer(cvb); + + // set sizes + FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles); + VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size); + svb.setupData(Usage.Stream, 1, Format.Float, sb); + 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); + 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 000000000..8ce619734 --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleTriMesh.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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); + 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); + 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); + + 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); + 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().multLocal(p.size); + camDir.cross(left, up); + up.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/package.html b/engine/src/core/com/jme3/effect/package.html new file mode 100644 index 000000000..eecc0b6bb --- /dev/null +++ b/engine/src/core/com/jme3/effect/package.html @@ -0,0 +1,19 @@ + + + + + + + + + +The com.jme3.effect package allows particle emitter effects to be +used with a jME3 application.
+

+ The ParticleEmitter class is the primary class used to create + particle emitter effects. See the jme3test.effect package + for examples on how to use ParticleEmitters. + + + + 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 000000000..16ae5c3f3 --- /dev/null +++ b/engine/src/core/com/jme3/export/InputCapsule.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 InputCapsule { + + // 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 readFloatBufferArrayList(String name, ArrayList defVal) throws IOException; + public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException; + + + // Maps + + public Map readSavableMap(String name, Map defVal) throws IOException; + public Map readStringSavableMap(String name, Map defVal) throws IOException; + public IntMap readIntSavableMap(String name, IntMap 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 readEnum(String name, Class 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 000000000..5cacd1e32 --- /dev/null +++ b/engine/src/core/com/jme3/export/JmeExporter.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.export; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +public interface JmeExporter { + + public boolean save(Savable object, OutputStream f) throws IOException; + public boolean save(Savable object, File f) throws IOException; + + 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 000000000..7c756f89e --- /dev/null +++ b/engine/src/core/com/jme3/export/JmeImporter.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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(); +} 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 000000000..fecda9be7 --- /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 array, String name, ArrayList defVal) throws IOException; + public void writeByteBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException; + + + // Maps + + public void writeSavableMap(Map map, String name, Map defVal) throws IOException; + public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException; + public void writeIntSavableMap(IntMap map, String name, IntMap 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 000000000..bb87b3ace --- /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 000000000..9171ffcd3 --- /dev/null +++ b/engine/src/core/com/jme3/export/Savable.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.export; + +import java.io.IOException; + +/** + * Savable is an interface for objects that can be serialized + * using jME's serialization system. + * @author Dany + */ +public interface Savable { + void write(JmeExporter ex) throws IOException; + void read(JmeImporter im) throws IOException; +} 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 000000000..bf635d6d7 --- /dev/null +++ b/engine/src/core/com/jme3/font/BitmapCharacter.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.font; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 kerning = new IntMap(); + 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 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 000000000..f31ce8c0e --- /dev/null +++ b/engine/src/core/com/jme3/font/BitmapCharacterSet.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.font; + +import java.io.IOException; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; + +public class BitmapCharacterSet implements Savable { + + private int lineHeight; + private int base; + private int renderedSize; + private int width; + private int height; + private IntMap> 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> entry : characters) { + int style = entry.getKey(); + styles[index] = style; + index++; + IntMap charset = entry.getValue(); + writeCharset(oc, style, charset); + } + oc.write(styles, "styles", null); + } + + protected void writeCharset(OutputCapsule oc, int style, IntMap charset) throws IOException { + int size = charset.size(); + short[] indexes = new short[size]; + BitmapCharacter[] chars = new BitmapCharacter[size]; + int i = 0; + for (Entry 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 readCharset(InputCapsule ic, int style) throws IOException { + IntMap charset = new IntMap(); + 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>(); + } + + public BitmapCharacter getCharacter(int index){ + return getCharacter(index, 0); + } + + public BitmapCharacter getCharacter(int index, int style){ + IntMap map = getCharacterSet(style); + return map.get(index); + } + + private IntMap getCharacterSet(int style) { + if (characters.size() == 0) { + characters.put(style, new IntMap()); + } + 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> entry : styleSet.characters) { + int style = entry.getKey(); + if (style == 0) { + throw new RuntimeException("Style must be set first. use setStyle(int)"); + } + IntMap charset = entry.getValue(); + this.lineHeight = Math.max(this.lineHeight, styleSet.lineHeight); + IntMap old = this.characters.put(style, charset); + if (old != null) { + throw new RuntimeException("Can't override old style"); + } + + for (Entry 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> entry = characters.iterator().next(); + IntMap 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 000000000..b90614707 --- /dev/null +++ b/engine/src/core/com/jme3/font/BitmapFont.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.font; + +import java.io.IOException; +import java.util.Arrays; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; + +/** + * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator + * @author dhdd + */ +public class BitmapFont implements Savable { + + public enum Align { + Left, Center, Right + } + public enum VAlign { + Top, Center, 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){ + 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; + } + BitmapCharacter c = charSet.getCharacter((int) theChar); + if (c != null){ + if (theChar == '\\' && isetBox() 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 000000000..5f3ce4a98 --- /dev/null +++ b/engine/src/core/com/jme3/font/BitmapTextPage.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.font; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.LinkedList; + +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; + +/** + * 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 pageQuads = new LinkedList(); + + 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 000000000..e1559e720 --- /dev/null +++ b/engine/src/core/com/jme3/font/ColorTags.java @@ -0,0 +1,92 @@ +package com.jme3.font; + +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.jme3.math.ColorRGBA; + +/** + * 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 colors = new LinkedList(); + private String text; + + ColorTags() { } + + ColorTags(String seq) { + setText(seq); + } + + /** + * @return text without color tags + */ + String getPlainText() { + return text; + } + + LinkedList 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 000000000..0b2bf4011 --- /dev/null +++ b/engine/src/core/com/jme3/font/Kerning.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.font; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 000000000..edb3f5308 --- /dev/null +++ b/engine/src/core/com/jme3/font/LetterQuad.java @@ -0,0 +1,470 @@ +package com.jme3.font; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +import com.jme3.math.ColorRGBA; + +/** + * 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; + } else { + x0 = previous.getNextX() + 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; + } + } + 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 000000000..eece896a6 --- /dev/null +++ b/engine/src/core/com/jme3/font/Letters.java @@ -0,0 +1,311 @@ +package com.jme3.font; + +import java.util.LinkedList; + +import com.jme3.font.BitmapFont.Align; +import com.jme3.font.BitmapFont.VAlign; +import com.jme3.font.ColorTags.Range; +import com.jme3.math.ColorRGBA; + +/** + * 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(); + + 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)); + } + } + + LinkedList 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); + } + } + + 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(); + } + } + + /** + * @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 000000000..3c77d6eb3 --- /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 000000000..33b47c540 --- /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 000000000..9f6f055a2 --- /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. setBox() 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 000000000..1c2300da4 --- /dev/null +++ b/engine/src/core/com/jme3/input/ChaseCamera.java @@ -0,0 +1,900 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.MouseAxisTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.controls.Trigger; +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 { + + private Spatial target = null; + private float minVerticalRotation = 0.00f; + private float maxVerticalRotation = FastMath.PI / 2; + private float minDistance = 1.0f; + private float maxDistance = 40.0f; + private float distance = 20; + private float zoomSpeed = 2f; + private float rotationSpeed = 1.0f; + private float rotation = 0; + private float trailingRotationInertia = 0.05f; + private float zoomSensitivity = 5f; + private float rotationSensitivity = 5f; + private float chasingSensitivity = 5f; + private float trailingSensitivity = 0.5f; + private float vRotation = FastMath.PI / 6; + private boolean smoothMotion = false; + private boolean trailingEnabled = true; + private float rotationLerpFactor = 0; + private float trailingLerpFactor = 0; + private boolean rotating = false; + private boolean vRotating = false; + private float targetRotation = rotation; + private InputManager inputManager; + private Vector3f initialUpVec; + private float targetVRotation = vRotation; + private float vRotationLerpFactor = 0; + private float targetDistance = distance; + private float distanceLerpFactor = 0; + private boolean zooming = false; + private boolean trailing = false; + private boolean chasing = false; + private boolean canRotate; + private float offsetDistance = 0.002f; + private Vector3f prevPos; + private boolean targetMoves = false; + private boolean enabled = true; + private Camera cam = null; + private final Vector3f targetDir = new Vector3f(); + private float previousTargetRotation; + private 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; + private Vector3f temp = new Vector3f(0, 0, 0); + protected boolean invertYaxis = false; + protected boolean invertXaxis = false; + private final static String ChaseCamDown = "ChaseCamDown"; + private final static String ChaseCamUp = "ChaseCamUp"; + private final static String ChaseCamZoomIn = "ChaseCamZoomIn"; + private final static String ChaseCamZoomOut = "ChaseCamZoomOut"; + private final static String ChaseCamMoveLeft = "ChaseCamMoveLeft"; + private final static String ChaseCamMoveRight = "ChaseCamMoveRight"; + private 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 on ly be used internally + * @param tpf + */ + public void update(float tpf) { + updateCamera(tpf); + } + + /** + * renders the camera control, should on ly 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); + } + + /** + * + * @deprecated use getMaxVerticalRotation() + */ + @Deprecated + public float getMaxHeight() { + return getMaxVerticalRotation(); + } + + /** + * + * @deprecated use setMaxVerticalRotation() + */ + @Deprecated + public void setMaxHeight(float maxHeight) { + setMaxVerticalRotation(maxHeight); + } + + /** + * + * @deprecated use getMinVerticalRotation() + */ + @Deprecated + public float getMinHeight() { + return getMinVerticalRotation(); + } + + /** + * + * @deprecated use setMinVerticalRotation() + */ + @Deprecated + public void setMinHeight(float minHeight) { + setMinVerticalRotation(minHeight); + } + + /** + * 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 + * @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 + * @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. + * @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. + * @param trailingRotationInertia + */ + public void setTrailingRotationInertia(float trailingRotationInertia) { + this.trailingRotationInertia = trailingRotationInertia; + } + + /** + * returns the trailing sensitivity + * @return + */ + public float getTrailingSensitivity() { + return trailingSensitivity; + } + + /** + * 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; + } + + /** + * + * @param invertYaxis + * @deprecated use setInvertVerticalAxis + */ + @Deprecated + public void setInvertYaxis(boolean invertYaxis) { + setInvertVerticalAxis(invertYaxis); + } + + /** + * 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 invertYaxis + */ + 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 000000000..3a62a6e86 --- /dev/null +++ b/engine/src/core/com/jme3/input/FlyByCamera.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.JoyAxisTrigger; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseAxisTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +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 { + + 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 (!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; + } + + /** + * @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; + inputManager.setCursorVisible(dragToRotate); + } + + /** + * Registers the FlyByCamera to receive input events from the provided + * Dispatcher. + * @param dispacher + */ + public void registerWithInput(InputManager inputManager){ + this.inputManager = inputManager; + + 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" + }; + + // 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); + + 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()); + } + } + + 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 000000000..1cece68e0 --- /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 000000000..cf43b8830 --- /dev/null +++ b/engine/src/core/com/jme3/input/InputManager.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; + +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.JoyAxisTrigger; +import com.jme3.input.controls.JoyButtonTrigger; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseAxisTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.controls.Trigger; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +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 InputManager 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. + * + * By default a dispatcher 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. + */ +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 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> bindings = new IntMap>(); + private final HashMap mappings = new HashMap(); + private final IntMap pressedButtons = new IntMap(); + private final IntMap axisValues = new IntMap(); + private ArrayList rawListeners = new ArrayList(); + private ArrayList inputQueue = new ArrayList(); + + private static class Mapping { + + private final String name; + private final ArrayList triggers = new ArrayList(); + private final ArrayList listeners = new ArrayList(); + + public Mapping(String name) { + this.name = name; + } + } + + /** + * Initializes the InputManager. + * + * @param mouseInput + * @param keyInput + * @param joyInput + * @throws IllegalArgumentException If either mouseInput or keyInput are null. + */ + public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick) { + if (keys == null || mouse == null) { + throw new NullPointerException("Mouse or keyboard cannot be null"); + } + + this.keys = keys; + this.mouse = mouse; + this.joystick = joystick; + + keys.setInputListener(this); + mouse.setInputListener(this); + if (joystick != null) { + joystick.setInputListener(this); + joysticks = joystick.loadJoysticks(this); + } + + firstTime = keys.getInputTimeNanos(); + } + + private void invokeActions(int hash, boolean pressed) { + ArrayList 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 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 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 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 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 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 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 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); + } + + } + } + } + + public void beginInput() { + } + + 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); + invokeAnalogsAndActions(hash, -value, true); + axisValues.put(hash, -value); + } else { + int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + invokeAnalogsAndActions(hash, value, true); + axisValues.put(hash, value); + } + } + + 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()); + } + + 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); + } + } + + 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) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onMouseButtonEvent(evt); +// } + + int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + public void onMouseButtonEvent(MouseButtonEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + private void onKeyEventQueued(KeyInputEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onKeyEvent(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()); + } + + public void onKeyEvent(KeyInputEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + public void setAxisDeadZone(float deadZone) { + this.axisDeadZone = deadZone; + } + + 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); + } + } + } + + public void removeListener(InputListener listener) { + for (Mapping mapping : mappings.values()) { + mapping.listeners.remove(listener); + } + } + + 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.hashCode(); + ArrayList names = bindings.get(hash); + if (names == null) { + names = new ArrayList(); + 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); + } + } + } + + public void deleteMapping(String mappingName) { + Mapping mapping = mappings.remove(mappingName); + if (mapping == null) { + throw new IllegalArgumentException("Cannot find mapping: " + mappingName); + } + + ArrayList triggers = mapping.triggers; + for (int i = triggers.size() - 1; i >= 0; i--) { + int hash = triggers.get(i); + ArrayList maps = bindings.get(hash); + maps.remove(mapping); + } + } + + public void deleteTrigger(String mappingName, Trigger trigger) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + throw new IllegalArgumentException("Cannot find mapping: " + mappingName); + } + + ArrayList maps = bindings.get(trigger.hashCode()); + 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(); + } + + /** + * Called to reset pressed keys or buttons when focus is restored. + */ + public void reset() { + pressedButtons.clear(); + axisValues.clear(); + } + + /** + * @param visible whether the mouse cursor is visible or not. + */ + public boolean isCursorVisible() { + return mouseVisible; + } + + /** + * @param visible whether the mouse cursor should be visible or not. + */ + public void setCursorVisible(boolean visible) { + if (mouseVisible != visible) { + mouseVisible = visible; + mouse.setCursorVisible(mouseVisible); + } + } + + public Vector2f getCursorPosition() { + return cursorPos; + } + + public Joystick[] getJoysticks() { + return joysticks; + } + + public void addRawInputListener(RawInputListener listener) { + rawListeners.add(listener); + } + + public void removeRawInputListener(RawInputListener listener) { + rawListeners.remove(listener); + } + + public void clearRawInputListeners() { + rawListeners.clear(); + } + + private void processQueue() { + int queueSize = inputQueue.size(); + int numRawListeners = rawListeners.size(); + + for (int i = 0; i < numRawListeners; i++) { + RawInputListener listener = rawListeners.get(i); + 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 { + 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 { + assert false; + } + } + + inputQueue.clear(); + } + + /** + * Updates the Dispatcher. 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; + safeMode = tpf < 0.015f; + long currentTime = keys.getInputTimeNanos(); + frameDelta = currentTime - lastUpdateTime; + + eventsPermitted = true; + + keys.update(); + mouse.update(); + if (joystick != null) { + joystick.update(); + } + + eventsPermitted = false; + + processQueue(); + invokeUpdateActions(); + + lastLastUpdateTime = lastUpdateTime; + lastUpdateTime = currentTime; + } +} 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 000000000..a867e95ab --- /dev/null +++ b/engine/src/core/com/jme3/input/JoyInput.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.input; + +/** + * A specific API for interfacing with joysticks or gaming controllers. + */ +public interface JoyInput extends Input { + + public static final int AXIS_POV_X = 254; + public static final int AXIS_POV_Y = 255; + + public void setJoyRumble(int joyId, float amount); + 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 000000000..28fc7daed --- /dev/null +++ b/engine/src/core/com/jme3/input/Joystick.java @@ -0,0 +1,72 @@ +package com.jme3.input; + +import com.jme3.input.controls.JoyAxisTrigger; +import com.jme3.input.controls.JoyButtonTrigger; + +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; + + 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; + } + + public void rumble(float amount){ + joyInput.setJoyRumble(joyId, amount); + } + + public void assignButton(String mappingName, int buttonId){ + if (buttonId < 0 || buttonId >= buttonCount) + throw new IllegalArgumentException(); + + inputManager.addMapping(mappingName, new JoyButtonTrigger(joyId, buttonId)); + } + + 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)); + } + + public int getXAxisIndex(){ + return axisXIndex; + } + + public int getYAxisIndex(){ + return axisYIndex; + } + + public int getAxisCount() { + return axisCount; + } + + public int getButtonCount() { + return buttonCount; + } + + 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 000000000..507d729c2 --- /dev/null +++ b/engine/src/core/com/jme3/input/KeyInput.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + /** + * @ 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; + public static final int KEY_LMETA = 0xDB; /* Left Windows/Option key */ + /** + * The left windows key, mapped to KEY_LMETA + * + * @Deprecated Use KEY_LMETA instead + */ + public static final int KEY_LWIN = KEY_LMETA; /* Left Windows key */ + public static final int KEY_RMETA = 0xDC; /* Right Windows/Option key */ + /** + * The right windows key, mapped to KEY_RMETA + * + * @Deprecated Use KEY_RMETA instead + */ + public static final int KEY_RWIN = KEY_RMETA; /* Right Windows key */ + 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/MouseInput.java b/engine/src/core/com/jme3/input/MouseInput.java new file mode 100644 index 000000000..9211c529a --- /dev/null +++ b/engine/src/core/com/jme3/input/MouseInput.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 com.jme3.input; + +/** + * A specific API for interfacing with the mouse. + */ +public interface MouseInput extends Input { + + public static final int AXIS_X = 0, + AXIS_Y = 1, + AXIS_WHEEL = 2; + + public static final int BUTTON_LEFT = 0, + BUTTON_RIGHT = 1, + BUTTON_MIDDLE = 2; + + /** + * @param visible Whether the mouse cursor should be visible or not. + */ + public void setCursorVisible(boolean visible); + + /** + * @return The number of buttons the mouse has. Typically 3 for most mice. + */ + 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 000000000..1f49573e0 --- /dev/null +++ b/engine/src/core/com/jme3/input/RawInputListener.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; + +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; + +/** + * An interface used for receiving raw input from devices. + */ +public interface RawInputListener { + + public void beginInput(); + public void endInput(); + + public void onJoyAxisEvent(JoyAxisEvent evt); + public void onJoyButtonEvent(JoyButtonEvent evt); + public void onMouseMotionEvent(MouseMotionEvent evt); + public void onMouseButtonEvent(MouseButtonEvent evt); + public void onKeyEvent(KeyInputEvent evt); +} 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 000000000..79e2a29c4 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/ActionListener.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; + +/** + * ActionListener is used to receive input events in "digital" style. + * Generally all button inputs, such as keyboard, mouse button, and joystick button, + * will be represented exactly. Analog inputs will be converted into digital. + * + * 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 value + * set to true, and will be invoked again when the button is released, + * with value set to false. + * + * @author Kirill Vainer + */ +public interface ActionListener extends InputListener { + 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 000000000..d1481f659 --- /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; + +/** + * AnalogListener is used to receive events of inputs + * in analog format. As a value from 0 to 1. + * + * @author Kirill Vainer + */ +public interface AnalogListener extends InputListener { + /** + * Called to notify the implementation that an analog event has occured. + * + * The results of KeyTrigger and MouseButtonTrigger events will have tpf + * == value. + * + * @param name - the name of the analog event + * @param value - how much the axis changed during this event + * @param tpf - how much time has passed since the last frame + */ + 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 000000000..ce140a992 --- /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 000000000..fc60f4044 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.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; + +public class JoyAxisTrigger implements Trigger { + + private final int joyId, axisId; + private final boolean negative; + + 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); + } + + @Override + public int hashCode(){ + return joyAxisHash(joyId, axisId, negative); + } + + 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+"]"; + } + +} 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 000000000..9fe454103 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.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.controls; + +public class JoyButtonTrigger implements Trigger { + + private final int joyId, buttonId; + + 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); + } + + @Override + public int hashCode(){ + return joyButtonHash(joyId, buttonId); + } + + public int getAxisId() { + return buttonId; + } + + public int getJoyId() { + return joyId; + } + + public String getName() { + return "JoyButton[joyId="+joyId+", axisId="+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 000000000..7b4dcd262 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/KeyTrigger.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.controls; + +/** + * A KeyTrigger is used as a mapping to keyboard keys. + * + * @author Kirill Vainer + */ +public class KeyTrigger implements Trigger { + + private final int keyCode; + + 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; + } + + @Override + public int hashCode(){ + 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 000000000..772e8eac6 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.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.controls; + +import com.jme3.input.MouseInput; + +/** + * A MouseAxisTrigger 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; + + 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 final int mouseAxisHash(int mouseAxis, boolean negative){ + assert mouseAxis >= 0 && mouseAxis <= 255; + return (negative ? 768 : 512) | (mouseAxis & 0xff); + } + + @Override + public int hashCode(){ + 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 000000000..b1ecb0cc3 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.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; + +/** + * A MouseButtonTrigger 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; + + 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); + } + + @Override + public int hashCode(){ + return mouseButtonHash(mouseButton); + } + +} 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 000000000..dde8ebac4 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/Trigger.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; + +/** + * 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(); + + /** + * @return Hash-code for the trigger, can map into the entire + * 32 bit space, and there must be no two different triggers with same + * hash-code. + */ + @Override + public int hashCode(); +} 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 000000000..21097b527 --- /dev/null +++ b/engine/src/core/com/jme3/input/dummy/DummyInput.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.dummy; + +import com.jme3.input.Input; +import com.jme3.input.RawInputListener; + +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 000000000..41d31b0a7 --- /dev/null +++ b/engine/src/core/com/jme3/input/dummy/DummyKeyInput.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.input.dummy; + +import com.jme3.input.KeyInput; + +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 000000000..155640023 --- /dev/null +++ b/engine/src/core/com/jme3/input/dummy/DummyMouseInput.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.input.dummy; + +import com.jme3.input.MouseInput; + +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/event/InputEvent.java b/engine/src/core/com/jme3/input/event/InputEvent.java new file mode 100644 index 000000000..981a51995 --- /dev/null +++ b/engine/src/core/com/jme3/input/event/InputEvent.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.input.event; + +/** + * An abstract input event. + */ +public abstract class InputEvent { + + /** + * Time in ticks when the event occured. + */ + protected long time; + + /** + * If the input event has been consumed, meaning it is no longer valid + * and should not be forwarded to input listeners. + */ + protected boolean consumed = false; + + public long getTime(){ + return time; + } + + public void setTime(long time){ + this.time = time; + } + + public boolean isConsumed() { + return consumed; + } + + 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 000000000..3115a6585 --- /dev/null +++ b/engine/src/core/com/jme3/input/event/JoyAxisEvent.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.input.event; + +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; + } + + public int getAxisIndex() { + return axisIdx; + } + + public int getJoyIndex() { + return joyIdx; + } + + 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 000000000..9ffc27c1a --- /dev/null +++ b/engine/src/core/com/jme3/input/event/JoyButtonEvent.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.input.event; + +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; + } + + public int getButtonIndex() { + return btnIdx; + } + + public int getJoyIndex() { + return joyIdx; + } + + 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 000000000..498dbaacd --- /dev/null +++ b/engine/src/core/com/jme3/input/event/KeyInputEvent.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.event; + +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; + } + + public char getKeyChar() { + return keyChar; + } + + public int getKeyCode() { + return keyCode; + } + + public boolean isPressed() { + return pressed; + } + + public boolean isRepeating() { + return repeating; + } + + public boolean isReleased() { + return !pressed; + } + + 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 000000000..a10a8293d --- /dev/null +++ b/engine/src/core/com/jme3/input/event/MouseButtonEvent.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.event; + +public class MouseButtonEvent extends InputEvent { + + int x; + int y; + int btnIndex; + boolean pressed; + + public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) { + this.btnIndex = btnIndex; + this.pressed = pressed; + this.x = x; + this.y = y; + } + + public int getButtonIndex() { + return btnIndex; + } + + public boolean isPressed() { + return pressed; + } + + public boolean isReleased() { + return !pressed; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + 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 000000000..e120e4bd9 --- /dev/null +++ b/engine/src/core/com/jme3/input/event/MouseMotionEvent.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.event; + +import com.jme3.input.*; + +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; + } + + public int getDeltaWheel() { + return deltaWheel; + } + + public int getDX() { + return dx; + } + + public int getDY() { + return dy; + } + + public int getWheel() { + return wheel; + } + + public int getX() { + return x; + } + + 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/light/AmbientLight.java b/engine/src/core/com/jme3/light/AmbientLight.java new file mode 100644 index 000000000..3fc9be061 --- /dev/null +++ b/engine/src/core/com/jme3/light/AmbientLight.java @@ -0,0 +1,16 @@ +package com.jme3.light; + +import com.jme3.scene.Spatial; + +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 000000000..6b59f79a1 --- /dev/null +++ b/engine/src/core/com/jme3/light/DirectionalLight.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.light; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * A light coming from a certain direction in world space. E.g sun or moon light. + */ +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 + } + + /** + * @return The direction vector of the light. + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction of the light. + * @param dir Represents the vector direction the light is coming from. + * (1f, 0, 0) would represent a directional light coming from the X axis. + */ + 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 000000000..ef4c5ed66 --- /dev/null +++ b/engine/src/core/com/jme3/light/Light.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 com.jme3.light; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Abstract class for representing a light source. + */ +public abstract class Light implements Savable, Cloneable { + + public static enum Type { + + Directional(0), + Point(1), + Spot(2), + Ambient(3); + + private int typeId; + + Type(int type){ + this.typeId = 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 take effect. + */ + protected boolean enabled = true; + + /** + * @return The color of the light. + */ + public ColorRGBA getColor() { + return color; + } + + public void setLastDistance(float lastDistance){ + this.lastDistance = lastDistance; + } + + public float getLastDistance(){ + return lastDistance; + } + + public void setColor(ColorRGBA color){ + this.color.set(color); + } + + 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); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + color = (ColorRGBA) ic.readSavable("color", null); + enabled = ic.readBoolean("enabled", true); + } + + public abstract void computeLastDistance(Spatial owner); + 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 000000000..394e22cbc --- /dev/null +++ b/engine/src/core/com/jme3/light/LightList.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.light; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.scene.Spatial; +import com.jme3.util.SortUtil; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +public final class LightList implements Iterable, 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 c = new Comparator() { + /** + * 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(){ + } + + 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; + } + + 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; + } + + 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, 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; + } + } + + public Iterator iterator() { + return new Iterator(){ + + 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 lights = new ArrayList(); + 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 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 000000000..b38523e4d --- /dev/null +++ b/engine/src/core/com/jme3/light/PointLight.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.light; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.math.ColorRGBA; +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. + */ +public class PointLight extends Light { + + protected Vector3f position = new Vector3f(); + protected float radius = 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); + } + } + + public Vector3f getPosition() { + return position; + } + + public void setPosition(Vector3f position){ + this.position.set(position); + } + + public float getRadius(){ + return radius; + } + + public void setRadius(float radius){ + this.radius = radius; + } + + @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); + } + +} 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 000000000..c1c13865a --- /dev/null +++ b/engine/src/core/com/jme3/material/FixedFuncBinding.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.material; + +public enum FixedFuncBinding { + MaterialAmbient, + MaterialDiffuse, + MaterialSpecular, + Color, +} 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 000000000..9e9a696e2 --- /dev/null +++ b/engine/src/core/com/jme3/material/MatParam.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.material; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.renderer.GL1Renderer; +import com.jme3.renderer.Renderer; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import java.io.IOException; + +public class MatParam implements Savable, Cloneable { + + protected VarType type; + protected String name; + protected Object value; + protected FixedFuncBinding ffBinding; +// protected Uniform uniform; + + public MatParam(VarType type, String name, Object value, FixedFuncBinding ffBinding){ + this.type = type; + this.name = name; + this.value = value; + this.ffBinding = ffBinding; + } + + public MatParam(){ + } + + public FixedFuncBinding getFixedFuncBinding() { + return ffBinding; + } + + public VarType getVarType() { + return type; + } + + public String getName(){ + return name; + } + + public void setName(String name){ + this.name=name; + } + + public Object getValue(){ + return value; + } + + public void setValue(Object value){ + this.value = value; + } + +// public Uniform getUniform() { +// return uniform; +// } +// +// public void setUniform(Uniform uniform) { +// this.uniform = uniform; +// } + + @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 String toString(){ + return type.name()+" "+name; + } + + public void apply(Renderer r, Technique technique) { + TechniqueDef techDef = technique.getDef(); + if (techDef.isUsingShaders()) { + technique.updateUniformParam(getName(), getVarType(), getValue(), true); + } + if (ffBinding != null && r instanceof GL1Renderer){ + ((GL1Renderer)r).setFixedFuncBinding(ffBinding, getValue()); + } + } +} + 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 000000000..ef85fb6d4 --- /dev/null +++ b/engine/src/core/com/jme3/material/Material.java @@ -0,0 +1,907 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AssetKey; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.asset.AssetManager; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.material.TechniqueDef.LightMode; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +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 java.io.IOException; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Material implements Cloneable, Savable, Comparable { + + 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 MaterialDef def; + private ListMap paramValues = new ListMap(); + private Technique technique; + private HashMap techniques = new HashMap(); + 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 static class MatParamTexture extends MatParam { + + private Texture texture; + private int unit; +// private transient TextureKey key; + + 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(getName(), 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); +// key = texture.getTextureKey(); + } + } + + public Material(MaterialDef def) { + if (def == null) { + throw new NullPointerException("Material definition cannot be null"); + } + + this.def = def; + } + + public Material(AssetManager contentMan, String defName) { + this((MaterialDef) contentMan.loadAsset(new AssetKey(defName))); + } + + /** + * Do not use this constructor. Serialization purposes only. + */ + public Material() { + } + + 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; + } + + public int compareTo(Material m) { + return m.getSortId() - getSortId(); + } + + @Override + public Material clone() { + try { + Material mat = (Material) super.clone(); + if (additionalState != null) { + mat.additionalState = additionalState.clone(); + } + mat.technique = null; + mat.techniques = new HashMap(); + + mat.paramValues = new ListMap(); + for (int i = 0; i < paramValues.size(); i++) { + Map.Entry entry = paramValues.getEntry(i); + mat.paramValues.put(entry.getKey(), entry.getValue().clone()); + } + + return mat; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public Technique getActiveTechnique() { + return technique; + } + + /** + * Should only be used in Technique.makeCurrent() + * @param tech + */ + public void setActiveTechnique(Technique tech) { + technique = tech; + } + + public boolean isTransparent() { + return transparent; + } + + public void setTransparent(boolean transparent) { + this.transparent = transparent; + } + + public boolean isReceivesShadows() { + return receivesShadows; + } + + public void setReceivesShadows(boolean receivesShadows) { + this.receivesShadows = receivesShadows; + } + + public RenderState getAdditionalRenderState() { + if (additionalState == null) { + additionalState = RenderState.ADDITIONAL.clone(); + } + return additionalState; + } + + public MaterialDef getMaterialDef() { + return def; + } + +// void updateUniformLinks(){ +// for (MatParam param : paramValues.values()){ +// param.uniform = technique.getShader().getUniform(param.name); +// } +// } + public MatParam getParam(String name) { + MatParam param = paramValues.get(name); + if (param instanceof MatParam) { + return (MatParam) param; + } + + return null; + } + + public MatParamTexture getTextureParam(String name) { + MatParam param = paramValues.get(name); + if (param instanceof MatParamTexture) { + return (MatParamTexture) param; + } + return null; + } + + public Collection 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.logp(Level.WARNING, "Material parameter being set: {0} with " + + "type {1} doesn't match definition type {2}", + 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 @see com.jme3.shaderVarType + * @param value the value of the param + */ + 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 param must exist + * @param name the name of the parameter to clear + */ + public void clearParam(String name) { + 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."); +// } + } + + /** + * + * @param name + * @deprecated use clearParam instead + */ + @Deprecated + public void clearTextureParam(String name) { + name = checkSetParam(null, name); + + MatParamTexture val = getTextureParam(name); + if (val == null) { + throw new IllegalArgumentException("The given texture parameter is not set."); + } else { + 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); + } + } + } + } + } + + public void setTextureParam(String name, VarType type, Texture value) { + if (value == null) { + throw new NullPointerException(); + } + + 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); + } + } + + /** + * 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.

+ * *

+ * uniform vec4 g_LightColor[numLights];
+ * // g_LightColor.rgb is the diffuse/specular color of the light.
+ * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
+ * // 2 = Spot.
+ *
+ * uniform vec4 g_LightPosition[numLights];
+ * // g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
+ * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation)
+ *

+ * + * @param shader + * @param lightList + */ + 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"); + lightColor.setVector4Length(numLights); + lightPos.setVector4Length(numLights); + + for (int i = 0; i < numLights; i++) { + if (lightList.size() <= i) { + lightColor.setVector4InArray(0f, 0f, 0f, 0f, i); + lightPos.setVector4InArray(0f, 0f, 0f, 0f, i); + } 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, i); + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getRadius(); + if (invRadius != 0) { + invRadius = 1f / invRadius; + } + lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, i); + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + } + } + } + + protected void renderMultipassLighting(Shader shader, Geometry g, Renderer r) { + LightList lightList = g.getWorldLightList(); + 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; + } + + ColorRGBA color = l.getColor(); + ColorRGBA color2; + if (lightColor.getValue() != null) { + color2 = (ColorRGBA) lightColor.getValue(); + } else { + color2 = new ColorRGBA(); + } + color2.set(color); + color2.a = l.getType().getId(); + lightColor.setValue(VarType.Vector4, color2); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + Quaternion q1; + if (lightPos.getValue() != null) { + q1 = (Quaternion) lightPos.getValue(); + } else { + q1 = new Quaternion(); + } + q1.set(dir.getX(), dir.getY(), dir.getZ(), -1); + lightPos.setValue(VarType.Vector4, q1); + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getRadius(); + if (invRadius != 0) { + invRadius = 1f / invRadius; + } + Quaternion q2; + if (lightPos.getValue() != null) { + q2 = (Quaternion) lightPos.getValue(); + } else { + q2 = new Quaternion(); + } + q2.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); + lightPos.setValue(VarType.Vector4, q2); + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + + 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); + } + } + + 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 rendererCaps = renderManager.getRenderer().getCaps(); + + if (name.equals("Default")) { + List techDefs = def.getDefaultTechniques(); + if (techDefs == null || techDefs.size() == 0) { + throw new IllegalStateException("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()); + } + + 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()); + } + } + + /** + * "Pre-load" the material, including textures and shaders, to the + * renderer. + */ + public void preload(RenderManager rm) { + autoSelectTechnique(rm); + + Renderer r = rm.getRenderer(); + TechniqueDef techDef = technique.getDef(); + + Collection 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 uniforms = shader.getUniformMap(); + for (int i = 0; i < uniforms.size(); i++) { + Uniform u = uniforms.getValue(i); + u.clearSetByCurrentMaterial(); + } + } + + private void resetUniformsNotSetByCurrent(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + for (int i = 0; i < uniforms.size(); i++) { + Uniform u = uniforms.getValue(i); + if (!u.isSetByCurrentMaterial()) { + u.clearValue(); + } + } + } + + /** + * Should be called after selectTechnique() + * @param geom + * @param r + */ + 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, r); + // 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); + String defName = ic.readString("material_def", null); + def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName)); + additionalState = (RenderState) ic.readSavable("render_state", null); + transparent = ic.readBoolean("is_transparent", false); + + HashMap params = (HashMap) ic.readStringSavableMap("parameters", null); +// paramValues.putAll(params); + paramValues = new ListMap(); + + // load the textures and update nextTexUnit + for (Map.Entry 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.texture == null || texVal.texture.getImage() == null) { + continue; + } + } + param.setName(checkSetParam(param.getVarType(), param.getName())); + paramValues.put(param.getName(), param); + } + } +} 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 000000000..ecc617163 --- /dev/null +++ b/engine/src/core/com/jme3/material/MaterialDef.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.material; + +import com.jme3.asset.AssetManager; +import com.jme3.shader.VarType; +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; + +public class MaterialDef { + + private static final Logger logger = Logger.getLogger(MaterialDef.class.getName()); + + private String name; + private String assetName; + private AssetManager assetManager; + + private List defaultTechs; + private Map techniques; + private Map matParams; + + public MaterialDef(){ + } + + public MaterialDef(AssetManager assetManager, String name){ + this.assetManager = assetManager; + this.name = name; + techniques = new HashMap(); + matParams = new HashMap(); + defaultTechs = new ArrayList(); + logger.log(Level.INFO, "Loaded material definition: {0}", name); + } + + public void setAssetName(String assetName){ + this.assetName = assetName; + } + + public String getAssetName(){ + return assetName; + } + + public AssetManager getAssetManager(){ + return assetManager; + } + + public String getName(){ + return name; + } + + public void addMaterialParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) { + matParams.put(name, new MatParam(type, name, value, ffBinding)); + } + + public MatParam getMaterialParam(String name){ + return matParams.get(name); + } + + public void addTechniqueDef(TechniqueDef technique){ + if (technique.getName().equals("Default")){ + defaultTechs.add(technique); + }else{ + techniques.put(technique.getName(), technique); + } + } + + public List getDefaultTechniques(){ + return defaultTechs; + } + + 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 000000000..e40bd29c6 --- /dev/null +++ b/engine/src/core/com/jme3/material/MaterialList.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +public class MaterialList extends HashMap { +} 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 000000000..d11d624e3 --- /dev/null +++ b/engine/src/core/com/jme3/material/RenderState.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +public class RenderState implements Cloneable, Savable { + + public static final RenderState DEFAULT = new RenderState(); + public static final RenderState NULL = new RenderState(); + public static final RenderState ADDITIONAL = new RenderState(); + + public enum TestFunc { + + Never, + Equal, + Less, + LessOrEqual, + Greater, + GreaterOrEqual, + NotEqual, + Always, + } + + public enum BlendMode { + + /** + * No blending mode is used. + */ + Off, + /** + * Additive blending. For use with glows and particle emitters. + * + * Result = Source Color + Destination Color + */ + Additive, + /** + * Premultiplied alpha blending, for use with premult alpha textures. + * + * Result = Source Color + (Dest Color * 1 - Source Alpha) + */ + PremultAlpha, + /** + * Additive blending that is multiplied with source alpha. + * For use with glows and particle emitters. + * + * Result = (Source Alpha * Source Color) + Dest Color + */ + AlphaAdditive, + /** + * Color blending, blends in color from dest color + * using source color. + * + * Result = Source Color + (1 - Source Color) * Dest Color + */ + Color, + /** + * Alpha blending, interpolates to source color from dest color + * using source alpha. + * + * Result = Source Alpha * Source Color + + * (1 - Source Alpha) * Dest Color + */ + Alpha, + /** + * Multiplies the source and dest colors. + * + * Result = Source Color * Dest Color + */ + Modulate, + /** + * Multiplies the source and dest colors then doubles the result. + * + * Result = 2 * Source Color * Dest Color + */ + ModulateX2 + } + + public enum FaceCullMode { + + /** + * Face culling is disabled. + */ + Off, + /** + * Cull front faces + */ + Front, + /** + * Cull back faces + */ + Back, + /** + * Cull both front and back faces. + */ + FrontAndBack + } + + 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; + boolean offsetEnabled = false; + boolean applyPolyOffset = true; + float offsetFactor = 0; + float offsetUnits = 0; + + 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); + } + + 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); + } + + @Override + public RenderState clone() { + try { + return (RenderState) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public boolean isPointSprite() { + return pointSprite; + } + + public void setPointSprite(boolean pointSprite) { + applyPointSprite = true; + this.pointSprite = pointSprite; + } + + public boolean isColorWrite() { + return colorWrite; + } + + public float getPolyOffsetFactor() { + return offsetFactor; + } + + public float getPolyOffsetUnits() { + return offsetUnits; + } + + public boolean isPolyOffset() { + return offsetEnabled; + } + + public float getAlphaFallOff() { + return alphaFallOff; + } + + public void setAlphaFallOff(float alphaFallOff) { + applyAlphaFallOff = true; + this.alphaFallOff = alphaFallOff; + } + + public boolean isAlphaTest() { + return alphaTest; + } + + public void setAlphaTest(boolean alphaTest) { + applyAlphaTest = true; + this.alphaTest = alphaTest; + } + + public FaceCullMode getFaceCullMode() { + return cullMode; + } + + public void setColorWrite(boolean colorWrite) { + applyColorWrite = true; + this.colorWrite = colorWrite; + } + + /** + * 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 http://www.opengl.org/resources/faq/technical/polygonoffset.htm + * @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; + } + + public void setFaceCullMode(FaceCullMode cullMode) { + applyCullMode = true; + this.cullMode = cullMode; + } + + public BlendMode getBlendMode() { + return blendMode; + } + + public void setBlendMode(BlendMode blendMode) { + applyBlendMode = true; + this.blendMode = blendMode; + } + + public boolean isDepthTest() { + return depthTest; + } + + public void setDepthTest(boolean depthTest) { + applyDepthTest = true; + this.depthTest = depthTest; + } + + public boolean isDepthWrite() { + return depthWrite; + } + + public void setDepthWrite(boolean depthWrite) { + applyDepthWrite = true; + this.depthWrite = depthWrite; + } + + public boolean isWireframe() { + return wireframe; + } + + public void setWireframe(boolean wireframe) { + applyWireFrame = true; + this.wireframe = wireframe; + } + + 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; + } + + public RenderState copyMergedTo(RenderState additionalState,RenderState state) { + if (additionalState == null) { + return this; + } + + if (additionalState.isApplyPointSprite()) { + state.pointSprite = additionalState.pointSprite; + }else{ + state.pointSprite = pointSprite; + } + if (additionalState.isApplyWireFrame()) { + state.wireframe = additionalState.wireframe; + }else{ + state.wireframe = wireframe; + } + + if (additionalState.isApplyCullMode()) { + state.cullMode = additionalState.cullMode; + }else{ + state.cullMode = cullMode; + } + if (additionalState.isApplyDepthWrite()) { + state.depthWrite = additionalState.depthWrite; + }else{ + state.depthWrite = depthWrite; + } + if (additionalState.isApplyDepthTest()) { + state.depthTest = additionalState.depthTest; + }else{ + state.depthTest = depthTest; + } + if (additionalState.isApplyColorWrite()) { + state.colorWrite = additionalState.colorWrite; + }else{ + state.colorWrite = colorWrite; + } + if (additionalState.isApplyBlendMode()) { + state.blendMode = additionalState.blendMode; + }else{ + state.blendMode = blendMode; + } + if (additionalState.isApplyAlphaTest()) { + state.alphaTest = additionalState.alphaTest; + }else{ + state.alphaTest = alphaTest; + } + + if (additionalState.isApplyAlphaFallOff()) { + state.alphaFallOff = additionalState.alphaFallOff; + }else{ + state.alphaFallOff = alphaFallOff; + } + if (additionalState.isApplyPolyOffset()) { + state.offsetEnabled = additionalState.offsetEnabled; + state.offsetFactor = additionalState.offsetFactor; + state.offsetUnits = additionalState.offsetUnits; + }else{ + state.offsetEnabled = offsetEnabled; + state.offsetFactor = offsetFactor; + state.offsetUnits = offsetUnits; + } + return state; + } + + @Override + public String toString() { + return "RenderState{" + "pointSprite=" + pointSprite + "applyPointSprite=" + applyPointSprite + "wireframe=" + wireframe + "applyWireFrame=" + applyWireFrame + "cullMode=" + cullMode + "applyCullMode=" + applyCullMode + "depthWrite=" + depthWrite + "applyDepthWrite=" + applyDepthWrite + "depthTest=" + depthTest + "applyDepthTest=" + applyDepthTest + "colorWrite=" + colorWrite + "applyColorWrite=" + applyColorWrite + "blendMode=" + blendMode + "applyBlendMode=" + applyBlendMode + "alphaTest=" + alphaTest + "applyAlphaTest=" + applyAlphaTest + "alphaFallOff=" + alphaFallOff + "applyAlphaFallOff=" + applyAlphaFallOff + "offsetEnabled=" + offsetEnabled + "applyPolyOffset=" + applyPolyOffset + "offsetFactor=" + offsetFactor + "offsetUnits=" + offsetUnits + '}'; + } +} 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 000000000..628497600 --- /dev/null +++ b/engine/src/core/com/jme3/material/Technique.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderKey; +import com.jme3.shader.Uniform; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.VarType; +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 worldBindUniforms; + private DefineList defines; + private Shader shader; + private boolean needReload = true; + + public Technique(Material owner, TechniqueDef def) { + this.owner = owner; + this.def = def; + if (def.isUsingShaders()) { + this.worldBindUniforms = new ArrayList(); + this.defines = new DefineList(); + } + } + + public Technique() { + } + + 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(); + } + + public TechniqueDef getDef() { + return def; + } + + public Shader getShader() { + return shader; + } + + public List getWorldBindUniforms() { + return worldBindUniforms; + } + + /** + * @param paramName + */ + public void notifySetParam(String paramName, VarType type, Object value) { + String defineName = def.getShaderParamDefine(paramName); + if (defineName != null) { + defines.set(defineName, type, value); + needReload = true; + } + if (shader != null) { + updateUniformParam(paramName, type, value); + } + } + + /** + * @param paramName + */ + public void notifyClearParam(String paramName) { + String defineName = def.getShaderParamDefine(paramName); + if (defineName != null) { + defines.remove(defineName); + needReload = true; + } + if (shader != null) { + if (!paramName.startsWith("m_")) { + paramName = "m_" + paramName; + } + shader.removeUniform(paramName); + } + } + + void updateUniformParam(String paramName, VarType type, Object value, boolean ifNotOwner) { + if (!paramName.startsWith("m_")) { + paramName = "m_" + paramName; + } + 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); + } + + public boolean isNeedReload() { + return needReload; + } + + /** + * Prepares the technique for use by loading the shader and setting + * the proper defines based on material parameters. + */ + public void makeCurrent(AssetManager manager) { + // check if reload is needed.. + if (def.isUsingShaders()) { + DefineList newDefines = new DefineList(); + Collection 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(manager); + } + } + } + + private void loadShader(AssetManager manager) { + // recompute define list + DefineList allDefines = new DefineList(); + allDefines.addFrom(def.getShaderPresetDefines()); + allDefines.addFrom(defines); + + ShaderKey key = new ShaderKey(def.getVertName(), + def.getFragName(), + 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(); + for (UniformBinding binding : def.getWorldBindings()) { + Uniform uniform = shader.getUniform("g_" + binding.name()); + uniform.setBinding(binding); + if (uniform != null) { + worldBindUniforms.add(uniform); + + } + } + + needReload = false; + } +} 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 000000000..f8880694c --- /dev/null +++ b/engine/src/core/com/jme3/material/TechniqueDef.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.renderer.Caps; +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; + +public class TechniqueDef implements Savable { + + public enum LightMode { + Disable, + SinglePass, + MultiPass, + FixedPipeline, + } + + public enum ShadowMode { + Disable, + InPass, + PostPass, + } + + private EnumSet 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 defineParams; + private ArrayList worldBinds; + + public TechniqueDef(String name){ + this.name = name == null ? "Default" : name; + } + + /** + * Do not use this constructor. + */ + public TechniqueDef(){ + } + + 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 export is available +// oc.write(defineParams, "defineParams", null); + // TODO: Finish this when List 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); + } + + public String getName(){ + return name; + } + + public LightMode getLightMode() { + return lightMode; + } + + public void setLightMode(LightMode lightMode) { + this.lightMode = lightMode; + } + + public ShadowMode getShadowMode() { + return shadowMode; + } + + public void setShadowMode(ShadowMode shadowMode) { + this.shadowMode = shadowMode; + } + + public RenderState getRenderState() { + return renderState; + } + + public void setRenderState(RenderState renderState) { + this.renderState = renderState; + } + + public boolean isUsingShaders(){ + return usesShaders; + } + + public EnumSet getRequiredCaps() { + return requiredCaps; + } + + public void setShaderFile(String vert, String frag, String lang){ + this.vertName = vert; + this.fragName = frag; + this.shaderLang = lang; + + Caps langCap = Caps.valueOf(lang); + requiredCaps.add(langCap); + + usesShaders = true; + } + + public DefineList getShaderPresetDefines() { + return presetDefines; + } + + public String getShaderParamDefine(String paramName){ + if (defineParams == null) + return null; + + return defineParams.get(paramName); + } + + public void addShaderParamDefine(String paramName, String defineName){ + if (defineParams == null) + defineParams = new HashMap(); + + defineParams.put(paramName, defineName); + } + + public void addShaderPresetDefine(String defineName, VarType type, Object value){ + if (presetDefines == null) + presetDefines = new DefineList(); + + presetDefines.set(defineName, type, value); + } + + public String getFragName() { + return fragName; + } + + public String getVertName() { + return vertName; + } + + public String getShaderLanguage() { + return shaderLang; + } + + public boolean addWorldParam(String name) { + if (worldBinds == null){ + worldBinds = new ArrayList(); + } + for (UniformBinding binding : UniformBinding.values()) { + if (binding.name().equals(name)) { + worldBinds.add(binding); + return true; + } + } + return false; + } + + public List getWorldBindings() { + return worldBinds; + } + +} 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 000000000..6c9d3dc60 --- /dev/null +++ b/engine/src/core/com/jme3/math/AbstractTriangle.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.math; + +import com.jme3.collision.*; + +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 000000000..f254154d9 --- /dev/null +++ b/engine/src/core/com/jme3/math/ColorRGBA.java @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + + +/** + * ColorRGBA 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 { + + private static final long serialVersionUID = 1L; + /** + * 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 ColorRGBA object. This + * color is the default "white" with all values 1. + * + */ + public ColorRGBA() { + r = g = b = a = 1.0f; + } + + /** + * Constructor instantiates a new ColorRGBA 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 ColorRGBA object, based on + * a provided color. + * @param rgba the ColorRGBA object to copy. + */ + public ColorRGBA(ColorRGBA rgba) { + this.a = rgba.a; + this.r = rgba.r; + this.g = rgba.g; + this.b = rgba.b; + } + + /** + * + * set 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; + } + + /** + * set 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; + } + + /** + * clamp 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; + } + } + + /** + * + * getColorArray 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; + } + + /** + * + * randomColor 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; + } + + /** + * toString returns the string representation of this color. + * The format of the string is:
+ * : [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; + } + + /** + * equals 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; + } + + /** + * hashCode 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; + } +} 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 000000000..9145e4f5c --- /dev/null +++ b/engine/src/core/com/jme3/math/Eigen3f.java @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.Logger; + +public class Eigen3f { + 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.info(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 000000000..b0b3b4887 --- /dev/null +++ b/engine/src/core/com/jme3/math/FastMath.java @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +/** + * FastMath 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); + } + + /**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) { + double c1, c2, c3, c4; + c1 = p1; + c2 = -1.0 * 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; + } + + + /** + * Returns the arc cosine of an angle given in radians.
+ * Special cases: + *
  • If fValue is smaller than -1, then the result is PI. + *
  • If the argument is greater than 1, then the result is 0.
+ * @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.
+ * Special cases: + *
  • If fValue is smaller than -1, then the result is -HALF_PI. + *
  • If the argument is greater than 1, then the result is HALF_PI.
+ * @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.
+ * @param fValue The angle, in radians. + * @return fValue's asin + * @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; + } + + /** + * 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 0.0f (inclusive) to + * 1.0f (exclusive). + */ + public static float nextRandomFloat() { + return rand.nextFloat(); + } + + /** + * Returns a random float between min and max. + * + * @return A random int between min (inclusive) to + * max (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). + * + * Source: http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf + * + * @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 000000000..4776e70fc --- /dev/null +++ b/engine/src/core/com/jme3/math/Line.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.math; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.io.Serializable; +import java.nio.FloatBuffer; + +/** + * Line 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 { + //todo: merge with Ray? + private static final long serialVersionUID = 1L; + + private Vector3f origin; + private Vector3f direction; + + /** + * Constructor instantiates a new Line object. The origin and + * direction are set to defaults (0,0,0). + * + */ + public Line() { + origin = new Vector3f(); + direction = new Vector3f(); + } + + /** + * Constructor instantiates a new Line 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; + } + + /** + * + * getOrigin returns the origin of the line. + * @return the origin of the line. + */ + public Vector3f getOrigin() { + return origin; + } + + /** + * + * setOrigin sets the origin of the line. + * @param origin the origin of the line. + */ + public void setOrigin(Vector3f origin) { + this.origin = origin; + } + + /** + * + * getDirection returns the direction of the line. + * @return the direction of the line. + */ + public Vector3f getDirection() { + return direction; + } + + /** + * + * setDirection 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(); + assert vars.lock(); + 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(); + assert vars.unlock(); + 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(); + assert vars.lock(); + 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); + + assert vars.unlock(); + } + + /** + * + * random determines a random point along the line. + * @return a random point on the line. + */ + public Vector3f random() { + return random(null); + } + + /** + * random 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 000000000..c4dca2c37 --- /dev/null +++ b/engine/src/core/com/jme3/math/LineSegment.java @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.TempVars; +import java.io.IOException; + +/** + *

LineSegment represents a segment in the space. This is a portion of a Line + * that has a limited start and end points.

+ *

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.

+ *

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.

+ * + * @see Ray + * @author Mark Powell + * @author Joshua Slack + */ +public class LineSegment implements Cloneable, Savable { + + private static final long serialVersionUID = 1L; + + 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(); + } + + /** + *

Creates a new LineSegment with the given origin, direction and extent.

+ *

Note that the origin is not one of the ends of the LineSegment, but its center.

+ */ + public LineSegment(Vector3f origin, Vector3f direction, float extent) { + this.origin = origin; + this.direction = direction; + this.extent = extent; + } + + /** + *

Creates a new LineSegment with a given origin and end. This constructor will calculate the + * center, the direction and the extent.

+ */ + 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(); + 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) { + assert TempVars.get().lock(); + Vector3f compVec1 = TempVars.get().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(); + assert TempVars.get().unlock(); + return len; + } + + public float distanceSquared(LineSegment test) { + assert TempVars.get().lock(); + Vector3f compVec1 = TempVars.get().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(); + assert TempVars.get().unlock(); + 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(); + } + } + + /** + *

Evaluates whether a given point is contained within the axis aligned bounding box + * that contains this LineSegment.

This function is float error aware.

+ */ + public boolean isPointInsideBounds(Vector3f point) { + return isPointInsideBounds(point, Float.MIN_VALUE); + } + + /** + *

Evaluates whether a given point is contained within the axis aligned bounding box + * that contains this LineSegment.

This function accepts an error parameter, which + * is added to the extent of the bounding box.

+ */ + 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 000000000..0f2584a8d --- /dev/null +++ b/engine/src/core/com/jme3/math/Matrix3f.java @@ -0,0 +1,1298 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +/** + * Matrix3f 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 { + + 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 Matrix3f 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 Matrix3f 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); + } + + /** + * copy 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; + } + + /** + * get retrieves a value from the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i + * the row index. + * @param j + * the colum index. + * @return the value at (i, j). + */ + 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."); + } + + /** + * get(float[]) 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()."); + } + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f 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); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f 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; + } + + /** + * getColumn returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f 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); + } + + /** + * getRow returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f 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; + } + + /** + * toFloatBuffer 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; + } + + /** + * fillFloatBuffer 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); + } + return fb; + } + + /** + * + * setColumn 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; + } + + + /** + * + * setRow 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; + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i + * the row index. + * @param j + * the colum index. + * @param value + * the value for (i, j). + * @return this + */ + 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."); + } + + /** + * + * set 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; + } + + /** + * set 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); + } + + /** + * set 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; + } + + /** + * + * set defines the values of the matrix based on a supplied + * Quaternion. 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); + } + + /** + * loadIdentity 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); + } + + /** + * fromAngleAxis 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); + } + + /** + * fromAngleNormalAxis 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; + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * mult multiplies this matrix by a given + * Vector3f 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal multiplies this matrix by a given + * Vector3f 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; + } + + /** + * mult 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) <= FastMath.FLT_EPSILON ) + 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; + } + + /** + * determinant generates the determinate of this matrix. + * + * @return the determinate + */ + 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; + } + + /** + * add adds the values of a parameter matrix to this matrix. + * + * @param mat + * the matrix to add to this. + */ + @Deprecated + public void add(Matrix3f mat) { + m00 += mat.m00; + m01 += mat.m01; + m02 += mat.m02; + m10 += mat.m10; + m11 += mat.m11; + m12 += mat.m12; + m20 += mat.m20; + m21 += mat.m21; + m22 += mat.m22; + } + + /** + * transpose locally 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(); + } + + /** + * transposeNew 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; + } + + /** + * toString 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
[
+ * 1.0 0.0 0.0
+ * 0.0 1.0 0.0
+ * 0.0 0.0 1.0
]
+ * + * @return the string representation of this object. + */ + public String toString() { + StringBuffer result = new StringBuffer("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(); + } + + /** + * + * hashCode 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() + */ + 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 + */ + 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); + } + } + + /** + * scale 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 final 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 000000000..4c8e19506 --- /dev/null +++ b/engine/src/core/com/jme3/math/Matrix4f.java @@ -0,0 +1,2298 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +/** + * Matrix4f 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 { + + 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 Matrix 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 Matrix 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); + } + + /** + * copy 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(); + assert vars.lock(); + + 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 = TempVars.get().tempMat4; + transMatrix.loadIdentity(); + transMatrix.m03 = -location.x; + transMatrix.m13 = -location.y; + transMatrix.m23 = -location.z; + this.multLocal(transMatrix); + + assert TempVars.get().unlock(); + +// transMatrix.multLocal(this); + +// set(transMatrix); + } + + /** + * get 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); + } + + /** + * set 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; + } + } + + /** + * get retrieves a value from the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i + * the row index. + * @param j + * the colum index. + * @return the value at (i, j). + */ + 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."); + } + + /** + * getColumn 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); + } + + /** + * getColumn 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; + } + + /** + * + * setColumn 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); + } + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i + * the row index. + * @param j + * the colum index. + * @param value + * the value for (i, j). + */ + 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."); + } + + /** + * set 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]; + } + + /** + * set 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; + } + + /** + * set 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); + } + + /** + * set 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; + } + + /** + * transpose 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; + } + + /** + * toFloatBuffer returns a FloatBuffer object that contains + * the matrix data. + * + * @return matrix data as a FloatBuffer. + */ + public FloatBuffer toFloatBuffer() { + return toFloatBuffer(false); + } + + /** + * toFloatBuffer 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; + } + + /** + * fillFloatBuffer 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); + } + + /** + * fillFloatBuffer 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); + } + 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; + } + } + + /** + * readFloatBuffer 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); + } + + /** + * readFloatBuffer 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; + } + + /** + * loadIdentity 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); + } + } + + /** + * fromAngleAxis 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); + } + + /** + * fromAngleNormalAxis 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; + } + + /** + * mult 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; + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * mult 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); + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * mult multiplies a Vector4f about a rotation + * matrix. The resulting vector is returned as a new Vector4f. + * + * @param vec + * vec to multiply against. + * @return the rotated vector. + */ + public Vector4f mult(Vector4f vec) { + return mult(vec, null); + } + + /** + * mult multiplies a Vector4f 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; + } + + /** + * mult 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) { + return multAcross(vec, null); + } + + /** + * mult 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; + } + + /** + * multNormal 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; + } + + /** + * multNormal 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; + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * determinant 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; + } + + /** + * add 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; + } + + /** + * setTranslation 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]; + } + + /** + * setTranslation 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; + } + + /** + * setTranslation 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; + } + + /** + * setInverseTranslation 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]; + } + + /** + * angleRotation 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 Vector3f + * 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; + } + + /** + * setRotationQuaternion builds a rotation from a + * Quaternion. + * + * @param quat + * the quaternion to build the rotation from. + * @throws NullPointerException + * if quat is null. + */ + public void setRotationQuaternion(Quaternion quat) { + quat.toRotationMatrix(this); + } + + /** + * setInverseRotationRadians 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); + } + + /** + * setInverseRotationDegrees 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); + } + + /** + * + * inverseTranslateVect 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; + } + + /** + * + * inverseTranslateVect 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; + } + + /** + * + * inverseTranslateVect 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; + } + + /** + * + * inverseRotateVect 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; + } + + /** + * toString 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
[
+ * 1.0 0.0 0.0 0.0
+ * 0.0 1.0 0.0 0.0
+ * 0.0 0.0 1.0 0.0
+ * 0.0 0.0 0.0 1.0
]
+ * + * @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(); + } + + /** + * + * hashCode 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 000000000..52a6f3e9a --- /dev/null +++ b/engine/src/core/com/jme3/math/Plane.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 com.jme3.math; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.logging.Logger; + +/** + * Plane 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 { + + 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; + + /** + * Constant of the plane. See formula in class definition. + */ + protected float constant; + + /** + * Constructor instantiates a new Plane object. This is the + * default object and contains a normal of (0,0,0) and a constant of 0. + */ + public Plane() { + normal = new Vector3f(); + } + + /** + * Constructor instantiates a new Plane 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) { + logger.warning("Normal was null, created default normal."); + normal = new Vector3f(); + } + this.normal = normal; + this.constant = constant; + } + + /** + * setNormal sets the normal of the plane. + * + * @param normal + * the new normal of the plane. + */ + public void setNormal(Vector3f normal) { + if (normal == null) { + logger.warning("Normal was null, created default normal."); + normal = new Vector3f(); + } + this.normal.set(normal); + } + + /** + * setNormal sets the normal of the plane. + * + * @param normal + * the new normal of the plane. + */ + public void setNormal(float x, float y, float z) { + if (normal == null) { + logger.warning("Normal was null, created default normal."); + normal = new Vector3f(); + } + this.normal.set(x,y,z); + } + + /** + * getNormal retrieves the normal of the plane. + * + * @return the normal of the plane. + */ + public Vector3f getNormal() { + return normal; + } + + /** + * setConstant sets the constant value that helps define the + * plane. + * + * @param constant + * the new constant value. + */ + public void setConstant(float constant) { + this.constant = constant; + } + + /** + * getConstant 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; + } + + /** + * pseudoDistance 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; + } + + /** + * whichSide 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); + } + + /** + * toString returns a string thta represents the string + * representation of this plane. It represents the normal as a + * Vector3f 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 000000000..6843e82c4 --- /dev/null +++ b/engine/src/core/com/jme3/math/Quaternion.java @@ -0,0 +1,1304 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.TempVars; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +/** + * Quaternion 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. + * + * Quaternion is defined by four floating point numbers: {x y z + * w}. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Quaternion implements Savable, Cloneable { + + 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 Quaternion 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 Quaternion 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 Quaternion 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 Quaternion object to be equal to the + * passed Quaternion 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 Quaternion object from a + * collection of rotation angles. + * + * @param angles + * the angles of rotation (x, y, z) that will define the + * Quaternion. + */ + public Quaternion(float[] angles) { + fromAngles(angles); + } + + /** + * Constructor instantiates a new Quaternion 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 Quaternion 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; + } + + /** + * fromAngles 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]); + } + + /** + * fromAngles 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: http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm + * + * @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; + } + + /** + * toAngles returns this quaternion converted to Euler + * rotation angles (yaw,roll,pitch).
+ * See http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm + * + * @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; + } + + /** + * + * fromRotationMatrix 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; + } + + /** + * toRotationMatrix 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); + } + + /** + * toRotationMatrix 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; + } + + /** + * toRotationMatrix 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; + } + + /** + * getRotationColumn returns one of three columns specified + * by the parameter. This column is returned as a Vector3f + * 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); + } + + /** + * getRotationColumn returns one of three columns specified + * by the parameter. This column is returned as a Vector3f + * 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; + } + + /** + * fromAngleAxis 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; + } + + /** + * fromAngleNormalAxis 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; + } + + /** + * toAngleAxis 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; + } + + /** + * slerp 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); + } + + /** + * add 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); + } + + /** + * add 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; + } + + /** + * subtract 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); + } + + /** + * subtract 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; + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * apply 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; + } + + /** + * + * fromAxes creates a Quaternion 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]); + } + + /** + * + * fromAxes creates a Quaternion 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); + } + + /** + * + * toAxes 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]); + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * dot 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; + } + + /** + * norm 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; + } + + /** + * normalize normalizes the current Quaternion + */ + public void normalize() { + float n = FastMath.invSqrt(norm()); + x *= n; + y *= n; + z *= n; + w *= n; + } + + /** + * inverse 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; + } + + /** + * inverse 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; + } + + /** + * negate inverts the values of the quaternion. + * + */ + public void negate() { + x *= -1; + y *= -1; + z *= -1; + w *= -1; + } + + /** + * + * toString creates the string representation of this + * Quaternion. The values of the quaternion are displace (x, + * y, z, w), in the following manner:
+ * (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 + ")"; + } + + /** + * equals 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; + } + + /** + * + * hashCode 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; + + } + + /** + * readExternal builds a quaternion from an + * ObjectInput object.
+ * 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(); + } + + /** + * writeExternal writes this quaternion out to a + * ObjectOutput 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); + } + + /** + * lookAt 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(); + assert vars.lock(); + 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 ); + assert vars.unlock(); + } + + 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 000000000..91d39f1eb --- /dev/null +++ b/engine/src/core/com/jme3/math/Ray.java @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.TempVars; +import java.io.IOException; + + +/** + * Ray 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 { + + //todo: merge with Line? + private static final long serialVersionUID = 1L; + + /** The ray's begining point. */ + public Vector3f origin; + /** The direction of the ray. */ + public Vector3f direction; + + public float limit = Float.POSITIVE_INFINITY; + +// protected static final Vector3f tempVa=new Vector3f(); +// protected static final Vector3f tempVb=new Vector3f(); +// protected static final Vector3f tempVc=new Vector3f(); +// protected static final Vector3f tempVd=new Vector3f(); + + /** + * Constructor instantiates a new Ray object. As default, the + * origin is (0,0,0) and the direction is (0,0,0). + * + */ + public Ray() { + origin = new Vector3f(); + direction = new Vector3f(); + } + + /** + * Constructor instantiates a new Ray 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) { + this.origin = origin; + this.direction = direction; + } + + /** + * intersect 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)); +// } + + /** + * intersect 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); +// } + + /** + * intersectWhere 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); + } + + /** + * intersectWhere 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 ); + } + + /** + * intersectWherePlanar 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); + } + + /** + * intersectWherePlanar 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 ); + } + + /** + * intersects 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(); + assert vars.lock(); + + 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 + 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 + assert vars.unlock(); + + // 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; + } + } + } + } + assert vars.unlock(); + 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; + } + + /** + * intersectWherePlanar 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(); + assert vars.lock(); + + 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(); + assert vars.unlock(); + return len; + } + + /** + * + * getOrigin retrieves the origin point of the ray. + * + * @return the origin of the ray. + */ + public Vector3f getOrigin() { + return origin; + } + + /** + * + * setOrigin sets the origin of the ray. + * @param origin the origin of the ray. + */ + public void setOrigin(Vector3f origin) { + this.origin.set(origin); + } + + /** + * getLimit returns the limit or the ray, aka the length. + * If the limit is not infinity, then this ray is a line with length + * limit. + * @return + */ + public float getLimit(){ + return limit; + } + + /** + * setLimit sets the limit of the ray. + * @param limit the limit of the ray. + * @see Ray#getLimit() + */ + public void setLimit(float limit){ + this.limit = limit; + } + + /** + * + * getDirection retrieves the direction vector of the ray. + * @return the direction of the ray. + */ + public Vector3f getDirection() { + return direction; + } + + /** + * + * setDirection sets the direction vector of the ray. + * @param direction the direction of the ray. + */ + public void setDirection(Vector3f direction) { + 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 000000000..22f46ccaa --- /dev/null +++ b/engine/src/core/com/jme3/math/Rectangle.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.io.Serializable; + + +/** + * + * Rectangle 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 Serializable, Savable, Cloneable { + private static final long serialVersionUID = 1L; + + private Vector3f a, b, c; + + /** + * Constructor creates a new Rectangle 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 Rectangle 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; + } + + /** + * getA returns the first point of the rectangle. + * + * @return the first point of the rectangle. + */ + public Vector3f getA() { + return a; + } + + /** + * setA sets the first point of the rectangle. + * + * @param a + * the first point of the rectangle. + */ + public void setA(Vector3f a) { + this.a = a; + } + + /** + * getB returns the second point of the rectangle. + * + * @return the second point of the rectangle. + */ + public Vector3f getB() { + return b; + } + + /** + * setB sets the second point of the rectangle. + * + * @param b + * the second point of the rectangle. + */ + public void setB(Vector3f b) { + this.b = b; + } + + /** + * getC returns the third point of the rectangle. + * + * @return the third point of the rectangle. + */ + public Vector3f getC() { + return c; + } + + /** + * setC sets the third point of the rectangle. + * + * @param c + * the third point of the rectangle. + */ + public void setC(Vector3f c) { + this.c = c; + } + + /** + * random 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); + } + + /** + * random 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 000000000..f9f53b8fc --- /dev/null +++ b/engine/src/core/com/jme3/math/Ring.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.math; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.io.Serializable; + + +/** + * Ring 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 Serializable, Savable, Cloneable { + private static final long serialVersionUID = 1L; + + private Vector3f center, up; + private float innerRadius, outerRadius; + private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f(); + + /** + * Constructor creates a new Ring 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 Ring 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; + } + + /** + * getCenter returns the center of the ring. + * + * @return the center of the ring. + */ + public Vector3f getCenter() { + return center; + } + + /** + * setCenter sets the center of the ring. + * + * @param center + * the center of the ring. + */ + public void setCenter(Vector3f center) { + this.center = center; + } + + /** + * getUp returns the ring's up vector. + * + * @return the ring's up vector. + */ + public Vector3f getUp() { + return up; + } + + /** + * setUp sets the ring's up vector. + * + * @param up + * the ring's up vector. + */ + public void setUp(Vector3f up) { + this.up = up; + } + + /** + * getInnerRadius returns the ring's inner radius. + * + * @return the ring's inner radius. + */ + public float getInnerRadius() { + return innerRadius; + } + + /** + * setInnerRadius sets the ring's inner radius. + * + * @param innerRadius + * the ring's inner radius. + */ + public void setInnerRadius(float innerRadius) { + this.innerRadius = innerRadius; + } + + /** + * getOuterRadius returns the ring's outer radius. + * + * @return the ring's outer radius. + */ + public float getOuterRadius() { + return outerRadius; + } + + /** + * setOuterRadius sets the ring's outer radius. + * + * @param outerRadius + * the ring's outer radius. + */ + public void setOuterRadius(float outerRadius) { + this.outerRadius = outerRadius; + } + + /** + * + * random returns a random point within the ring. + * + * @return a random point within the ring. + */ + public Vector3f random() { + return random(null); + } + + /** + * + * random 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 000000000..8ed8ad952 --- /dev/null +++ b/engine/src/core/com/jme3/math/Spline.java @@ -0,0 +1,324 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.math; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 + } + private List controlPoints = new ArrayList(); + private boolean cycle = false; + private List segmentsLength; + private float totalLength; + private List 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) { + for (int i = 0; i < controlPoints.length; i++) { + Vector3f vector3f = controlPoints[i]; + this.controlPoints.add(vector3f); + } + type = splineType; + this.curveTension = curveTension; + this.cycle = cycle; + 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 controlPoints, float curveTension, boolean cycle) { + type = splineType; + this.controlPoints.addAll(controlPoints); + this.curveTension = curveTension; + this.cycle = cycle; + computeTotalLentgh(); + } + + private void initCatmullRomWayPoints(List list) { + if (CRcontrolPoints == null) { + CRcontrolPoints = new ArrayList(); + } 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 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) { + 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) { + computeTotalLentgh(); + } + } + + private void computeTotalLentgh() { + totalLength = 0; + float l = 0; + if (segmentsLength == null) { + segmentsLength = new ArrayList(); + } 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 { + initCatmullRomWayPoints(controlPoints); + computeCatmulLength(); + } + } + + 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; + } + } + } + + /** + * 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); + default: + break; + } + return store; + } + + /** + * returns the curve tension + * @return + */ + public float getCurveTension() { + return curveTension; + } + + /** + * sets the curve tension + * + * @param curveTension the tension + */ + public void setCurveTension(float curveTension) { + this.curveTension = curveTension; + computeTotalLentgh(); + } + + /** + * returns true if the spline cycle + * @return + */ + public boolean isCycle() { + return cycle; + } + + /** + * set to true to make the spline cycle + * @param cycle + */ + public void setCycle(boolean cycle) { + 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; + computeTotalLentgh(); + } else { + this.cycle = cycle; + } + } + + /** + * return the total lenght of the spline + * @return + */ + public float getTotalLength() { + return totalLength; + } + + /** + * return the type of the spline + * @return + */ + public SplineType getType() { + return type; + } + + /** + * Sets the type of the spline + * @param type + */ + public void setType(SplineType type) { + this.type = type; + computeTotalLentgh(); + } + + /** + * returns this spline control points + * @return + */ + public List getControlPoints() { + return controlPoints; + } + + /** + * returns a list of float representing the segments lenght + * @return + */ + public List getSegmentsLength() { + return segmentsLength; + } + + @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); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + + controlPoints = (ArrayList) in.readSavableArrayList("wayPoints", null); + float list[] = in.readFloatArray("segmentsLength", null); + if (list != null) { + segmentsLength = new ArrayList(); + 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) in.readSavableArrayList("CRControlPoints", null); + curveTension = in.readFloat("curveTension", 0.5f); + cycle = in.readBoolean("cycle", false); + } +} 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 000000000..f451a67e9 --- /dev/null +++ b/engine/src/core/com/jme3/math/Transform.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + * Started Date: Jul 16, 2004

+ * Represents a translation, rotation and scale in one object. + * + * @author Jack Lindamood + * @author Joshua Slack + */ +public final class Transform implements Savable, Cloneable { + + 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){ + 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); + + parent + .rot + .multLocal(translation) + .multLocal(parent.scale) + .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(); + + in.subtract(translation, store).divideLocal(scale); + rot.inverse().mult(store, store); + 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 + "]"; + } + + /** + * 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.clone()); + scale = (Vector3f)capsule.readSavable("scale", Vector3f.UNIT_XYZ.clone()); + } + + @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 000000000..81028b0a8 --- /dev/null +++ b/engine/src/core/com/jme3/math/Triangle.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +/** + * Triangle defines a object for containing triangle information. + * The triangle is defined by a collection of three Vector3f + * objects. + * + * @author Mark Powell + * @author Joshua Slack + */ +public class Triangle extends AbstractTriangle implements Savable { + + 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 Triangle 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); + } + + /** + * + * get 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; + } + + /** + * + * set sets one of the triangles 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; + } + } + + /** + * + * set sets one of the triangles 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, 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); + } + + /** + * calculateCenter finds the average point of the 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 000000000..05a0974ad --- /dev/null +++ b/engine/src/core/com/jme3/math/Vector2f.java @@ -0,0 +1,760 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + +/** + * Vector2f defines a Vector for a two float value vector. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Vector2f implements Savable, Cloneable { + private static final Logger logger = Logger.getLogger(Vector2f.class.getName()); + + private static final long serialVersionUID = 1L; + + 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; + } + + /** + * add 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); + } + + /** + * addLocal 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; + } + + /** + * addLocal 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; + } + + /** + * add adds this vector by vec and stores the + * result in result. + * + * @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; + } + + /** + * dot 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; + } + + /** + * cross 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; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared 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; + } + + /** + * distanceSquared 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); + } + + /** + * distanceSquared 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(float otherX, float otherY) { + double dx = x - otherX; + double dy = y - otherY; + return (float) (dx * dx + dy * dy); + } + + /** + * distance 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)); + } + + /** + * mult 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); + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * divide 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 Vector. + */ + public Vector2f divide(float scalar) { + return new Vector2f(x / scalar, y / scalar); + } + + /** + * divideLocal 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; + } + + /** + * negate 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); + } + + /** + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector2f negateLocal() { + x = -x; + y = -y; + return this; + } + + /** + * subtract 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); + } + + /** + * subtract 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; + } + + /** + * subtract 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); + } + + /** + * subtractLocal 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; + } + + /** + * subtractLocal 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; + } + + /** + * normalize 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); + } + + /** + * normalizeLocal 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); + } + + /** + * smallestAngleBetween 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; + } + + /** + * angleBetween 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; + } + /** + * getAngle returns (in radians) the angle represented by + * this Vector2f as expressed by a conversion from rectangular coordinates (xy) + * to polar coordinates (r, theta). + * + * @return the angle in radians. [-pi, pi) + */ + public float getAngle() { + return FastMath.atan2(y, x); + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector2f zero() { + x = y = 0; + return this; + } + + /** + * hashCode 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; + } + + /** + * toString 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 000000000..4b713d7f3 --- /dev/null +++ b/engine/src/core/com/jme3/math/Vector3f.java @@ -0,0 +1,1063 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.logging.Logger; + +/* + * -- Added *Local methods to cut down on object creation - JS + */ + +/** + * Vector3f defines a Vector for a three float value tuple. + * Vector3f 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 { + + 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 Vector3f with default + * values of (0,0,0). + * + */ + public Vector3f() { + x = y = z = 0; + } + + /** + * Constructor instantiates a new Vector3f 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 Vector3f that is a copy + * of the provided vector + * @param copy The Vector3f to copy + */ + public Vector3f(Vector3f copy) { + this.set(copy); + } + + /** + * set 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; + } + + /** + * set 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; + } + + /** + * + * add 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); + } + + /** + * + * add 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; + } + + /** + * addLocal 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; + } + + /** + * + * add 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); + } + + /** + * addLocal 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; + } + + /** + * + * scaleAdd 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; + } + + /** + * + * scaleAdd 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; + } + + /** + * + * dot 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; + } + + /** + * cross 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); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @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); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @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; + } + + /** + * crossLocal 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); + } + + /** + * crossLocal 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; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared 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; + } + + /** + * distanceSquared 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); + } + + /** + * distance 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)); + } + + /** + * + * mult 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); + } + + /** + * + * mult 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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); + } + + /** + * multLocal 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); + } + + + /** + * divide 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 Vector. + */ + public Vector3f divide(float scalar) { + scalar = 1f/scalar; + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + /** + * divideLocal 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; + } + + + /** + * divide 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 Vector. + */ + public Vector3f divide(Vector3f scalar) { + return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z); + } + + /** + * divideLocal 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; + } + + /** + * + * negate 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); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector3f negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * + * subtract 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); + } + + /** + * subtractLocal 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; + } + + /** + * + * subtract + * + * @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; + } + + /** + * + * subtract 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); + } + + /** + * subtractLocal 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; + } + + /** + * normalize 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(); + } + + /** + * normalizeLocal 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; + } + + /** + * maxLocal computes the maximum value for each + * component in this and other 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; + } + + /** + * minLocal computes the minimum value for each + * component in this and other 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; + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector3f zero() { + x = y = z = 0; + return this; + } + + /** + * angleBetween 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; + } + + /** + * hashCode 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; + } + + /** + * toString 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 000000000..806908806 --- /dev/null +++ b/engine/src/core/com/jme3/math/Vector4f.java @@ -0,0 +1,1005 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.logging.Logger; + +/** + * Vector4f defines a Vector for a four float value tuple. + * Vector4f 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 { + + 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 Vector3f with default + * values of (0,0,0). + * + */ + public Vector4f() { + x = y = z = w = 0; + } + + /** + * Constructor instantiates a new Vector4f 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 Vector3f that is a copy + * of the provided vector + * @param copy The Vector3f to copy + */ + public Vector4f(Vector4f copy) { + this.set(copy); + } + + /** + * set 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; + } + + /** + * set 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; + } + + /** + * + * add 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); + } + + /** + * + * add 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; + } + + /** + * addLocal 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; + } + + /** + * + * add 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); + } + + /** + * addLocal 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; + } + + /** + * + * scaleAdd 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; + } + + /** + * + * scaleAdd 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; + } + + /** + * + * dot 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; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared 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; + } + + /** + * distanceSquared 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); + } + + /** + * distance 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)); + } + + /** + * + * mult 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); + } + + /** + * + * mult 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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); + } + + /** + * multLocal 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); + } + + /** + * divide 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 Vector. + */ + public Vector4f divide(float scalar) { + scalar = 1f/scalar; + return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar); + } + + /** + * divideLocal 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; + } + + /** + * divide 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 Vector. + */ + public Vector4f divide(Vector4f scalar) { + return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w); + } + + /** + * divideLocal 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; + } + + /** + * + * negate 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); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector4f negateLocal() { + x = -x; + y = -y; + z = -z; + w = -w; + return this; + } + + /** + * + * subtract 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); + } + + /** + * subtractLocal 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; + } + + /** + * + * subtract + * + * @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; + } + + /** + * + * subtract 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); + } + + /** + * subtractLocal 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 subtract@ + * 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; + } + + /** + * normalize 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(); + } + + /** + * normalizeLocal 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; + } + + /** + * maxLocal computes the maximum value for each + * component in this and other 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; + } + + /** + * minLocal computes the minimum value for each + * component in this and other 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; + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector4f zero() { + x = y = z = w = 0; + return this; + } + + /** + * angleBetween 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; + } + + /** + * hashCode 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; + } + + /** + * toString 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/post/Filter.java b/engine/src/core/com/jme3/post/Filter.java new file mode 100644 index 000000000..f78b7024a --- /dev/null +++ b/engine/src/core/com/jme3/post/Filter.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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; + +/** + * Filter abstract class + * Any Filter must extends this class + * Holds a frameBuffer and a texture + * The getMaterial must return a Material that use a GLSL shader immplementing the desired effect + */ +public abstract class Filter implements Savable { + + private String name; + protected Pass defaultPass; + protected List postRenderPasses; + protected Material material; + protected boolean enabled = true; + protected FilterPostProcessor processor; + + public Filter(String name) { + this.name = name; + } + + public class Pass { + + protected FrameBuffer renderFrameBuffer; + protected Texture2D renderedTexture; + protected Texture2D depthTexture; + protected Material passMaterial; + + public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples) { + Collection 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); + // depthTexture = new Texture2D(width, height, numSamples, depthBufferFormat); + } else { + renderFrameBuffer = new FrameBuffer(width, height, 1); + renderedTexture = new Texture2D(width, height, textureFormat); +// depthTexture = new Texture2D(width, height, depthBufferFormat); + } + + renderFrameBuffer.setColorTexture(renderedTexture); + renderFrameBuffer.setDepthBuffer(depthBufferFormat); + // renderFrameBuffer.setDepthTexture(depthTexture); + + } + + 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 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) { + } + } + + protected Format getDefaultPassTextureFormat() { + return Format.RGBA8; + } + + protected Format getDefaultPassDepthFormat() { + return Format.Depth; + } + + public Filter() { + this("filter"); + } + + public void init(FilterPostProcessor parentProcessor, AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + // cleanup(renderManager.getRenderer()); + defaultPass = new Pass(); + defaultPass.init(renderManager.getRenderer(),w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat()); + processor = parentProcessor; + initFilter(manager, renderManager, vp, w, h); + } + + public void cleanup(Renderer r) { + if (defaultPass != null) { + defaultPass.cleanup(r); + } + if (postRenderPasses != null) { + for (Iterator it = postRenderPasses.iterator(); it.hasNext();) { + Pass pass = it.next(); + pass.cleanup(r); + } + } + cleanUpFilter(r); + } + + /** + * This method is called once xhen the filter is added to the FilterPostProcessor + * It should contain Maerial initializations and extra passes initialization + * @param manager + */ + public abstract void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h); + + public abstract void cleanUpFilter(Renderer r); + + /** + * this method should return the material used for this filter. + * this method is called every frames + * @return + */ + public 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 + */ + public void preRender(RenderManager renderManager, ViewPort viewPort) { + } + + /** + * Use this method if you want to modify parameters according to tpf before the rendering of the frame. + * This is usefull for animated filters + * @param tpf the time used to render the previous frame + */ + public void preFrame(float tpf) { + } + + /** + * 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(ex); + * @param ex + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + name = ic.readString("name", ""); + enabled = ic.readBoolean("enabled", true); + + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public FrameBuffer getRenderFrameBuffer() { + return defaultPass.renderFrameBuffer; + } + + public void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) { + this.defaultPass.renderFrameBuffer = renderFrameBuffer; + } + + public Texture2D getRenderedTexture() { + return defaultPass.renderedTexture; + } + + public void setRenderedTexture(Texture2D renderedTexture) { + this.defaultPass.renderedTexture = renderedTexture; + } + + /** + * Override this method and retrun true if your Filter need the depth texture + * @return + */ + public boolean isRequiresDepthTexture() { + return false; + } + + public List getPostRenderPasses() { + return postRenderPasses; + } + + public void setPostRenderPasses(List postRenderPasses) { + this.postRenderPasses = postRenderPasses; + } + + public void setEnabled(boolean enabled) { + if(processor!=null){ + processor.setFilterState(this, enabled); + }else{ + this.enabled = enabled; + } + } + + public boolean isEnabled() { + return enabled; + } + +} 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 000000000..09079fcbc --- /dev/null +++ b/engine/src/core/com/jme3/post/FilterPostProcessor.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.post; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +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; + +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 filters = new ArrayList(); + 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 int lastFilterIndex = -1; + + /** + * Create a FilterProcessor constructor + * @param assetManager the Asset Manager + */ + public FilterPostProcessor(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * Don't use this constructor use FilterPostProcessor(AssetManager assetManager) + */ + public FilterPostProcessor() { + } + + public void addFilter(Filter filter) { + filters.add(filter); + + if (isInitialized()) { + initFilter(filter, viewPort); + } + if(filter.isEnabled()){ + lastFilterIndex = filters.size() - 1; + } + } + + public void removeFilter(Filter filter) { + for (Iterator it = filters.iterator(); it.hasNext();) { + if (it.next() == filter) { + it.remove(); + } + } + filter.cleanup(renderer); + updateLastFilterIndex(); + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderManager = rm; + renderer = rm.getRenderer(); + viewPort = vp; + fsQuad = new Picture("filter full screen quad"); + + reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); + } + + private void initFilter(Filter filter, ViewPort vp) { + filter.init(this, 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); + } + } + + private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) { + if (buff == null) { + fsQuad.setWidth(width); + fsQuad.setHeight(height); + filterCam.resize(width, height, true); + } else { + fsQuad.setWidth(buff.getWidth()); + fsQuad.setHeight(buff.getHeight()); + filterCam.resize(buff.getWidth(), buff.getHeight(), true); + } + fsQuad.setMaterial(mat); + fsQuad.updateGeometricState(); + renderManager.setCamera(filterCam, true); + r.setFrameBuffer(buff); + r.clearBuffers(true, true, true); + renderManager.renderGeometry(fsQuad); + } + + public boolean isInitialized() { + return viewPort != null; + } + + public void postQueue(RenderQueue rq) { + for (Iterator it = filters.iterator(); it.hasNext();) { + Filter filter = it.next(); + if (filter.isEnabled()) { + filter.preRender(renderManager, viewPort); + } + } + } + + public void renderFilterChain(Renderer r) { + Texture2D tex = filterTexture; + 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 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()); + } + } + + Material mat = filter.getMaterial(); + if (msDepth && filter.isRequiresDepthTexture()) { + mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples()); + } + + mat.setTexture("Texture", tex); + if (tex.getImage().getMultiSamples() > 1) { + mat.setInt("NumSamples", tex.getImage().getMultiSamples()); + } else { + mat.clearParam("NumSamples"); + } + + FrameBuffer buff = outputBuffer; + if (i != lastFilterIndex) { + buff = filter.getRenderFrameBuffer(); + tex = filter.getRenderedTexture(); + } + renderProcessing(r, buff, mat); + } + } + } + + public void postFrame(FrameBuffer out) { + //Added this to fix the issue where the filter were not rendered when an object in the scene had a DepthWrite to false. (particles for example) + //there should be a better way... + // renderer.applyRenderState(RenderState.DEFAULT); + if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL31)) { + renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer); + } + renderFilterChain(renderer); + } + + public void preFrame(float tpf) { + if (filters.isEmpty() || lastFilterIndex == -1) { + viewPort.setOutputFrameBuffer(outputBuffer); + } else { + if (renderFrameBufferMS != null) { + viewPort.setOutputFrameBuffer(renderFrameBufferMS); + } else { + viewPort.setOutputFrameBuffer(renderFrameBuffer); + } + } + for (Iterator it = filters.iterator(); it.hasNext();) { + Filter filter = it.next(); + if (filter.isEnabled()) { + filter.preFrame(tpf); + } + } + } + + /** + * Enable or disable a filter + * @param filter the filter + * @param enabled true to enable + * @deprecated use filter.setEnabled(boolean enabled) instead + */ + @Deprecated + public void setFilterEnabled(Filter filter, boolean enabled) { + if (filters.contains(filter)) { + filter.enabled = enabled; + updateLastFilterIndex(); + } + } + + protected void setFilterState(Filter filter, boolean enabled) { + if (filters.contains(filter)) { + filter.enabled = enabled; + updateLastFilterIndex(); + } + } + + private void updateLastFilterIndex() { + lastFilterIndex = -1; + for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) { + if (filters.get(i).isEnabled()) { + lastFilterIndex = i; + return; + } + } + + } + + /** + * return tru if the filter is enabled + * @param filter + * @return + */ + public boolean isFilterEnabled(Filter filter) { + return filter.isEnabled(); + } + + public void cleanup() { + if (viewPort != null) { + viewPort.setOutputFrameBuffer(outputBuffer); + viewPort = null; + } + } + + public void reshape(ViewPort vp, int w, int h) { + + width = Math.max(1, w); + height = Math.max(1, h); + vp.getCamera().resize(width, height, true); + computeDepth = false; + + if (renderFrameBuffer == null) { + outputBuffer = viewPort.getOutputFrameBuffer(); + } + + Collection 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; + // samplePositions = ((LwjglRenderer) renderer).getFrameBufferSamplePositions(renderFrameBufferMS); + } 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 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; + } + + /** + * Writes the processor + * @param ex + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(numSamples, "numSamples", 0); + oc.writeSavableArrayList((ArrayList) filters, "filters", null); + } + + /** + * Reads the processor + * @param im + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + numSamples = ic.readInt("numSamples", 0); + filters = ic.readSavableArrayList("filters", null); + } +} 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 000000000..2544aabb8 --- /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 000000000..7b986c7ca --- /dev/null +++ b/engine/src/core/com/jme3/post/SceneProcessor.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.post; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; + +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/renderer/Camera.java b/engine/src/core/com/jme3/renderer/Camera.java new file mode 100644 index 000000000..46be4da15 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Camera.java @@ -0,0 +1,1313 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.BoundingVolume; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Camera is a standalone, purely mathematical class for doing + * camera-related computations. + * + *

+ * Given input data such as location, orientation (direction, left, up), + * and viewport settings, it can compute data neccessary 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. + *

+ *

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

+ * + * @author Mark Powell + * @author Joshua Slack + */ +public class Camera implements Savable, Cloneable { + + private static final Logger logger = Logger.getLogger(Camera.class.getName()); + + 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; + } + //planes of the frustum + /** + * LEFT_PLANE represents the left plane of the camera frustum. + */ + public static final int LEFT_PLANE = 0; + /** + * RIGHT_PLANE represents the right plane of the camera frustum. + */ + public static final int RIGHT_PLANE = 1; + /** + * BOTTOM_PLANE represents the bottom plane of the camera frustum. + */ + public static final int BOTTOM_PLANE = 2; + /** + * TOP_PLANE represents the top plane of the camera frustum. + */ + public static final int TOP_PLANE = 3; + /** + * FAR_PLANE represents the far plane of the camera frustum. + */ + public static final int FAR_PLANE = 4; + /** + * NEAR_PLANE represents the near plane of the camera frustum. + */ + public static final int NEAR_PLANE = 5; + /** + * FRUSTUM_PLANES represents the number of planes of the camera frustum. + */ + public static final int FRUSTUM_PLANES = 6; + /** + * MAX_WORLD_PLANES holds the maximum planes allowed by the system. + */ + public static final int MAX_WORLD_PLANES = 6; + //the location and orientation of the camera. + /** + * 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(); + + /** + * Constructor instantiates a new Camera object. All + * values of the camera are set to default. + */ + public Camera(int width, int height) { + 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; + + worldPlane = new Plane[MAX_WORLD_PLANES]; + for (int i = 0; i < MAX_WORLD_PLANES; i++) { + worldPlane[i] = new Plane(); + } + + 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.update(); + + return cam; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * 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 + * http://www.terathon.com/code/oblique.html + * http://aras-p.info/texts/obliqueortho.html + * http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html + * + * 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 + * http://www.terathon.com/code/oblique.html + * http://aras-p.info/texts/obliqueortho.html + * http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html + * + * 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 renderer to notify the camera of + * changes in the display dimensions. + * + * @param width + * the view width + * @param height + * the view height + */ + 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(); + } + } + + /** + * getFrustumBottom returns the value of the bottom frustum + * plane. + * + * @return the value of the bottom frustum plane. + */ + public float getFrustumBottom() { + return frustumBottom; + } + + /** + * setFrustumBottom 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(); + } + + /** + * getFrustumFar gets the value of the far frustum plane. + * + * @return the value of the far frustum plane. + */ + public float getFrustumFar() { + return frustumFar; + } + + /** + * setFrustumFar 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(); + } + + /** + * getFrustumLeft gets the value of the left frustum plane. + * + * @return the value of the left frustum plane. + */ + public float getFrustumLeft() { + return frustumLeft; + } + + /** + * setFrustumLeft 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(); + } + + /** + * getFrustumNear gets the value of the near frustum plane. + * + * @return the value of the near frustum plane. + */ + public float getFrustumNear() { + return frustumNear; + } + + /** + * setFrustumNear 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(); + } + + /** + * getFrustumRight gets the value of the right frustum plane. + * + * @return frustumRight the value of the right frustum plane. + */ + public float getFrustumRight() { + return frustumRight; + } + + /** + * setFrustumRight 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(); + } + + /** + * getFrustumTop gets the value of the top frustum plane. + * + * @return the value of the top frustum plane. + */ + public float getFrustumTop() { + return frustumTop; + } + + /** + * setFrustumTop 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(); + } + + /** + * getLocation retrieves the location vector of the camera. + * + * @return the position of the camera. + * @see Camera#getLocation() + */ + public Vector3f getLocation() { + return location; + } + + /** + * getRotation retrieves the rotation quaternion of the camera. + * + * @return the rotation of the camera. + */ + public Quaternion getRotation() { + return rotation; + } + + /** + * getDirection 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); + } + + /** + * getLeft retrieves the left axis of the camera. + * + * @return the left axis of the camera. + * @see Camera#getLeft() + */ + public Vector3f getLeft() { + return rotation.getRotationColumn(0); + } + + /** + * getUp retrieves the up axis of the camera. + * + * @return the up axis of the camera. + * @see Camera#getUp() + */ + public Vector3f getUp() { + return rotation.getRotationColumn(1); + } + + /** + * getDirection 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); + } + + /** + * getLeft 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); + } + + /** + * getUp 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); + } + + /** + * setLocation sets the position of the camera. + * + * @param location the position of the camera. + * @see Camera#setLocation(com.jme.math.Vector3f) + */ + public void setLocation(Vector3f location) { + this.location.set(location); + onFrameChange(); + } + + /** + * setRotation sets the orientation of this camera. + * This will be equivelant to setting each of the axes: + *
+ * cam.setLeft(rotation.getRotationColumn(0));
+ * cam.setUp(rotation.getRotationColumn(1));
+ * cam.setDirection(rotation.getRotationColumn(2));
+ *
+ * + * @param rotation the rotation of this camera + */ + public void setRotation(Quaternion rotation) { + this.rotation.set(rotation); + onFrameChange(); + } + + /** + * setDirection sets the direction this camera is facing. In + * most cases, this changes the up and left vectors of the camera. If your + * left or up vectors change, you must updates those as well for correct + * culling. + * + * @param direction the direction this camera is facing. + * @see Camera#setDirection(com.jme.math.Vector3f) + */ + public void setDirection(Vector3f direction) { + //this.rotation.lookAt(direction, getUp()); + Vector3f left = getLeft(); + Vector3f up = getUp(); + this.rotation.fromAxes(left, up, direction); + onFrameChange(); + } + + /** + * setAxes 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.jme.math.Vector3f,com.jme.math.Vector3f,com.jme.math.Vector3f) + */ + public void setAxes(Vector3f left, Vector3f up, Vector3f direction) { + this.rotation.fromAxes(left, up, direction); + onFrameChange(); + } + + /** + * setAxes 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.normalize(); + onFrameChange(); + } + + /** + * setFrustum 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(); + } + + /** + * setFrustumPerspective 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(); + } + + /** + * setFrame 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(); + } + + /** + * lookAt 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(); + assert vars.lock(); + Vector3f newDirection = TempVars.get().vect1; + Vector3f newUp = TempVars.get().vect2; + Vector3f newLeft = TempVars.get().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.normalize(); + assert vars.unlock(); + + onFrameChange(); + } + + /** + * setFrame 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(); + } + + /** + * update updates the camera parameters by calling + * onFrustumChange,onViewPortChange and + * onFrameChange. + * + * @see Camera#update() + */ + public void update() { + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + } + + /** + * getPlaneState 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; + } + + /** + * setPlaneState sets the state to keep track of tested + * planes for culling. + * + * @param planeState the updated state. + */ + public void setPlaneState(int planeState) { + this.planeState = planeState; + } + + /** + * getViewPortLeft gets the left boundary of the viewport + * + * @return the left boundary of the viewport + */ + public float getViewPortLeft() { + return viewPortLeft; + } + + /** + * setViewPortLeft sets the left boundary of the viewport + * + * @param left the left boundary of the viewport + */ + public void setViewPortLeft(float left) { + viewPortLeft = left; + onViewPortChange(); + } + + /** + * getViewPortRight gets the right boundary of the viewport + * + * @return the right boundary of the viewport + */ + public float getViewPortRight() { + return viewPortRight; + } + + /** + * setViewPortRight sets the right boundary of the viewport + * + * @param right the right boundary of the viewport + */ + public void setViewPortRight(float right) { + viewPortRight = right; + onViewPortChange(); + } + + /** + * getViewPortTop gets the top boundary of the viewport + * + * @return the top boundary of the viewport + */ + public float getViewPortTop() { + return viewPortTop; + } + + /** + * setViewPortTop sets the top boundary of the viewport + * + * @param top the top boundary of the viewport + */ + public void setViewPortTop(float top) { + viewPortTop = top; + onViewPortChange(); + } + + /** + * getViewPortBottom gets the bottom boundary of the viewport + * + * @return the bottom boundary of the viewport + */ + public float getViewPortBottom() { + return viewPortBottom; + } + + /** + * setViewPortBottom sets the bottom boundary of the viewport + * + * @param bottom the bottom boundary of the viewport + */ + public void setViewPortBottom(float bottom) { + viewPortBottom = bottom; + onViewPortChange(); + } + + /** + * setViewPort 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) { + setViewPortLeft(left); + setViewPortRight(right); + setViewPortBottom(bottom); + setViewPortTop(top); + } + + /** + * 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); + } + + /** + * contains 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: + * BoundingVolume bv;
+ * Camera c;
+ * int planeState = bv.getPlaneState();
+ * bv.setPlaneState(0);
+ * c.contains(bv);
+ * bv.setPlaneState(plateState);
+ *
+ * + * @param bound the bound to check for culling + * @return See enums in FrustumIntersect + */ + 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; + } + + /** + * @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; + } + + /** + * onFrustumChange 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(); + } + + /** + * onFrameChange updates the view frame of the camera. + */ + public void onFrameChange() { + Vector3f left = getLeft(); + Vector3f direction = getDirection(); + Vector3f up = getUp(); + + 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); +// 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; + } + + /** + * @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; + } + + /** + * @see Camera#getScreenCoordinates + */ + public Vector3f getScreenCoordinates(Vector3f worldPos) { + return getScreenCoordinates(worldPos, null); + } + + /** + * Implementation contributed by Zbyl. + * + * @see Camera#getScreenCoordinates(Vector3f, Vector3f) + */ + public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + +// assert TempVars.get().lock(); +// Quaternion tmp_quat = TempVars.get().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; +// assert TempVars.get().unlock(); + + 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); + } + + 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); + 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", 0); + height = capsule.readInt("height", 0); + } +} 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 000000000..f9a02346b --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Caps.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.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; + +public enum Caps { + + /// Framebuffer features + /** + * Supports FrameBuffer Objects (FBO) + */ + FrameBuffer, + + /** + * Supports framebuffer Multiple Render Targets (MRT) + */ + FrameBufferMRT, + + /** + * Supports framebuffer multi-sampling + */ + FrameBufferMultisample, + + /** + * Supports texture multi-sampling + */ + TextureMultisample, + + /// API Version + OpenGL20, + OpenGL21, + OpenGL30, + OpenGL31, + OpenGL32, + + /// Shader language version + ARBprogram, + GLSL100, + GLSL110, + GLSL120, + GLSL130, + GLSL140, + GLSL150, + 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, + + /// Vertex Buffer features + MeshInstancing, + + /** + * Supports VAO, or vertex buffer arrays + */ + VertexBufferArray, + + /** + * Supports multisampling on the screen + */ + Multisample; + + public static boolean supports(Collection 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; + } + } + + public static boolean supports(Collection 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; + } + + public static boolean supports(Collection 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); + 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 000000000..dca7bb2a3 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/GL1Renderer.java @@ -0,0 +1,7 @@ +package com.jme3.renderer; + +import com.jme3.material.FixedFuncBinding; + +public interface GL1Renderer extends Renderer { + public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val); +} diff --git a/engine/src/core/com/jme3/renderer/GLObject.java b/engine/src/core/com/jme3/renderer/GLObject.java new file mode 100644 index 000000000..0db74731f --- /dev/null +++ b/engine/src/core/com/jme3/renderer/GLObject.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.logging.Level; +import java.util.logging.Logger; + +/** + * Describes a GL object. An encapsulation of a certain object + * on the native side of the graphics library. + * This class is used to track + */ +public abstract class GLObject 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 Type type; + + public static enum Type { + /** + * A texture is an image that is applied to geometry. + */ + Texture, + + /** + * Vertex buffers are used to describe geometry data and it's attributes. + */ + VertexBuffer, + + /** + * ShaderSource is a shader source code that controls the output of + * a certain rendering pipeline, like vertex position or fragment color. + */ + ShaderSource, + + /** + * A Shader is an aggregation of ShaderSources, collectively + * they cooperate to control the vertex and fragment processor. + */ + Shader, + + /** + * FrameBuffer is an offscreen surface which can be rendered to. + * Can be used to create "Render-to-Texture" effects and + * scene post processing. + */ + FrameBuffer, + } + + public GLObject(Type 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 GLObject(Type 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; + } + + public void setUpdateNeeded(){ + updateNeeded = true; + } + + /** + * + */ + public void clearUpdateNeeded(){ + updateNeeded = false; + } + + public boolean isUpdateNeeded(){ + return updateNeeded; + } + + @Override + public String toString(){ + return type.name() + " " + Integer.toHexString(hashCode()); + } + + /** + * This should create a deep clone. For a shallow clone, use + * createDestructableClone(). + * + * @return + */ + protected GLObject clone(){ + try{ + GLObject obj = (GLObject) super.clone(); + obj.handleRef = new Object(); + obj.id = -1; + obj.updateNeeded = true; + return obj; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + +// @Override +// public boolean equals(Object other){ +// if (this == other) +// return true; +// if (!(other instanceof GLObject)) +// return false; +// +// } + + // Specialized calls to be used by object manager only. + + /** + * 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 r + */ + public abstract void deleteObject(Renderer r); + + /** + * Creates a shallow clone of this GL Object. The deleteObject method + * should be functional for this object. + */ + public abstract GLObject createDestructableClone(); +} diff --git a/engine/src/core/com/jme3/renderer/GLObjectManager.java b/engine/src/core/com/jme3/renderer/GLObjectManager.java new file mode 100644 index 000000000..776942f64 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/GLObjectManager.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.renderer; + +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 + * ReferenceQueue the GLObjectManager 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 GLObjectManager { + + private static final Logger logger = Logger.getLogger(GLObjectManager.class.getName()); + + /** + * The queue will receive notifications of GLObjects which are no longer + * referenced. + */ + private ReferenceQueue refQueue = new ReferenceQueue(); + + /** + * List of currently active GLObjects. + */ + private ArrayList refList + = new ArrayList(); + + private class GLObjectRef extends PhantomReference{ + + private GLObject objClone; + private WeakReference realObj; + + public GLObjectRef(GLObject obj){ + super(obj.handleRef, refQueue); + assert obj.handleRef != null; + + this.realObj = new WeakReference(obj); + this.objClone = obj.createDestructableClone(); + } + } + + /** + * Register a GLObject with the manager. + */ + public void registerForCleanup(GLObject obj){ + GLObjectRef ref = new GLObjectRef(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(Renderer r){ + while (true){ + GLObjectRef ref = (GLObjectRef) refQueue.poll(); + if (ref == null) + return; + + refList.remove(ref); + ref.objClone.deleteObject(r); + 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(Renderer r){ + deleteUnused(r); + for (GLObjectRef ref : refList){ + ref.objClone.deleteObject(r); + GLObject 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 GLObjects. + */ + public void resetObjects(){ + for (GLObjectRef ref : refList){ + // here we use the actual obj not the clone, + // otherwise its useless + GLObject 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/renderer/IDList.java b/engine/src/core/com/jme3/renderer/IDList.java new file mode 100644 index 000000000..64f4f0672 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/IDList.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; + +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; + + public void reset(){ + newLen = 0; + oldLen = 0; + Arrays.fill(newList, 0); + Arrays.fill(oldList, 0); + } + + 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; + } + + public void copyNewToOld(){ + System.arraycopy(newList, 0, oldList, 0, newLen); + oldLen = newLen; + newLen = 0; + } + + 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 000000000..b1c1b745d --- /dev/null +++ b/engine/src/core/com/jme3/renderer/RenderContext.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.renderer; + +import com.jme3.material.RenderState; +import com.jme3.scene.VertexBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; + +/** + * 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 { + + /** + * If back-face culling is enabled. + */ + public RenderState.FaceCullMode cullMode = RenderState.FaceCullMode.Off; + + /** + * If Depth testing is enabled. + */ + public boolean depthTestEnabled = false; + + public boolean alphaTestEnabled = false; + + public boolean depthWriteEnabled = false; + + public boolean colorWriteEnabled = true; + + public boolean clipRectEnabled = false; + + public boolean polyOffsetEnabled = false; + public float polyOffsetFactor = 0; + public float polyOffsetUnits = 0; + + public boolean normalizeEnabled = false; + + public int matrixMode = -1; + + public float pointSize = 1; + public float lineWidth = 1; + + public RenderState.BlendMode blendMode = RenderState.BlendMode.Off; + + /** + * If wireframe rendering is enabled. False if fill rendering is enabled. + */ + public boolean wireframe = false; + + /** + * Point sprite mode + */ + public boolean pointSprite = false; + + /** + * The currently bound shader program. + */ + public int boundShaderProgram; + + /** + * Currently bound Framebuffer Object. + */ + public int boundFBO = 0; + + /** + * Currently bound Renderbuffer + */ + 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 + */ + public int boundDrawBuf = -1; + + /** + * Currently bound read buffer + * + * @see RenderContext#boundDrawBuf + */ + public int boundReadBuf = -1; + + /** + * Currently bound element array vertex buffer. + */ + public int boundElementArrayVBO; + + public int boundVertexArray; + + /** + * Currently bound array vertex buffer. + */ + public int boundArrayVBO; + + public int numTexturesSet = 0; + + /** + * Current bound texture IDs for each texture unit. + */ + public Image[] boundTextures = new Image[16]; + + public IDList textureIndexList = new IDList(); + + public int boundTextureUnit = 0; + + /** + * Vertex attribs currently bound and enabled. If a slot is null, then + * it is disabled. + */ + public VertexBuffer[] boundAttribs = new VertexBuffer[16]; + + public IDList attribIndexList = new IDList(); + + 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; + 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(); + } +} 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 000000000..383df350c --- /dev/null +++ b/engine/src/core/com/jme3/renderer/RenderManager.java @@ -0,0 +1,765 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AmbientLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.material.Technique; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +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.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.shader.Uniform; +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; + +/** + * RenderManager 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 preViewPorts = new ArrayList(); + private ArrayList viewPorts = new ArrayList(); + private ArrayList postViewPorts = new ArrayList(); + private Camera prevCam = null; + private Material forcedMaterial = null; + private String forcedTechnique = null; + private RenderState forcedRenderState = null; + private final 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; + + /** + * 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); + + } + + 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; + } + + public boolean removePreView(ViewPort view) { + return preViewPorts.remove(view); + } + + 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; + } + + 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; + } + + public boolean removeMainView(ViewPort view) { + return viewPorts.remove(view); + } + + 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; + } + + 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; + } + + public boolean removePostView(ViewPort view) { + return postViewPorts.remove(view); + } + + public List getPreViews() { + return Collections.unmodifiableList(preViewPorts); + } + + public List getMainViews() { + return Collections.unmodifiableList(viewPorts); + } + + public List getPostViews() { + return Collections.unmodifiableList(postViewPorts); + } + + /** + * Creates a new viewport, to display the given camera's content. + * The view will be processed before the primary viewport. + * @param viewName + * @param cam + * @return + */ + public ViewPort createPreView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + preViewPorts.add(vp); + return vp; + } + + public ViewPort createMainView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + viewPorts.add(vp); + return vp; + } + + 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 processors = vp.getProcessors(); + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { + proc.initialize(this, vp); + } else { + proc.reshape(vp, w, h); + } + } + } + + /** + * @param w + * @param h + */ + 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); + } + } + + public void updateUniformBindings(List params) { + // assums worldMatrix is properly set. + TempVars vars = TempVars.get(); + assert vars.lock(); + + 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; + case AmbientLightColor: + break; + } + } + + assert vars.unlock(); + } + + /** + * 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 + */ + public void setForcedMaterial(Material mat) { + forcedMaterial = mat; + } + + public RenderState getForcedRenderState() { + return forcedRenderState; + } + + public void setForcedRenderState(RenderState forcedRenderState) { + this.forcedRenderState = forcedRenderState; + } + + public void setWorldMatrix(Matrix4f mat) { + if (shader) { + worldMatrix.set(mat); + } else { + renderer.setWorldMatrix(mat); + } + } + + 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); + } + //re applying default render state at the end of the render to avoid depth write issues, MUST BE A BETTER WAY + renderer.applyRenderState(RenderState.DEFAULT); + } + + 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 through this + * recursive method. + * @param s + * @param r + */ + private void renderShadow(Spatial s, RenderQueue rq) { + if (s instanceof Node) { + Node n = (Node) s; + List 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); + } + } + } + + public void preloadScene(Spatial scene) { + if (scene instanceof Node) { + // recurse for all children + Node n = (Node) scene; + List 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 entry : mesh.getBuffers()) { + VertexBuffer buf = entry.getValue(); + if (buf.getData() != null) { + renderer.updateBufferData(buf); + } + } + } + } + } + + /** + * Render scene graph + * @param s + * @param r + * @param cam + */ + public void renderScene(Spatial scene, ViewPort vp) { + // check culling first. + if (!scene.checkCulling(vp.getCamera())) { + // move on to shadow-only render + if (scene.getShadowMode() != RenderQueue.ShadowMode.Off || scene instanceof Node) { + renderShadow(scene, vp.getQueue()); + } + return; + } + + scene.runControlRender(this, vp); + if (scene instanceof Node) { + // recurse for all children + Node n = (Node) scene; + List children = n.getChildren(); + for (int i = 0; i < children.size(); i++) { + 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); + } + } + } + + public Camera getCurrentCamera() { + return prevCam; + } + + public Renderer getRenderer() { + return renderer; + } + + /** + * Render the given viewport queues, flushing the geometryList + * @param vp the viewport + */ + public void flushQueue(ViewPort vp) { + renderViewPortQueues(vp, true); + } + + public void clearQueue(ViewPort vp) { + vp.getQueue().clear(); + } + + //Nehon 08/18/2010 changed flushQueue to renderViewPortQueues with a flush boolean param + /** + * Render the given viewport queues + * @param vp the viewport + * @param flush true to flush geometryList + */ + 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); + } + } + + 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, /*-1f*/ 0f); +// System.out.println(orthoMatrix); + } + } + + 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()); + } + + } + } + + public void setCamera(Camera cam, boolean ortho) { + setViewPort(cam); + setViewProjection(cam, ortho); + } + + /** + * Draws the viewport but doesn't invoke processors. + * @param vp + */ + public void renderViewPortRaw(ViewPort vp) { + setCamera(vp.getCamera(), false); + List scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + renderScene(scenes.get(i), vp); + } + flushQueue(vp); + } + + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; + } + List processors = vp.getProcessors(); + if (processors.size() == 0) { + 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.isClearEnabled()) { + renderer.setBackgroundColor(vp.getBackgroundColor()); + renderer.clearBuffers(vp.isClearColor(), + vp.isClearDepth(), + vp.isClearStencil()); + } + + List 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()); + } + } + + // clear any remaining spatials that were not rendered. + clearQueue(vp); + } + + public void render(float tpf) { + if (renderer instanceof NullRenderer) { + return; + } + + for (int i = 0; i < preViewPorts.size(); i++) { + renderViewPort(preViewPorts.get(i), tpf); + } + for (int i = 0; i < viewPorts.size(); i++) { + renderViewPort(viewPorts.get(i), tpf); + } + for (int i = 0; i < postViewPorts.size(); i++) { + renderViewPort(postViewPorts.get(i), tpf); + } + } + + //Remy - 09/14/2010 - added a setter for the timer in order to correctly populate g_Time and g_Tpf in the shaders + public void setTimer(Timer timer) { + this.timer = timer; + } + + public String getForcedTechnique() { + return forcedTechnique; + } + + public void setForcedTechnique(String forcedTechnique) { + this.forcedTechnique = forcedTechnique; + } + + public void setAlphaToCoverage(boolean value) { + renderer.setAlphaToCoverage(value); + } +} 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 000000000..8f25a823f --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Renderer.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.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; + +public interface Renderer { + + /** + * @return The capabilities of the renderer. + */ + public EnumSet getCaps(); + + /** + * @return The statistics allow tracking of how data + * per frame, such as number of objects rendered, number of triangles, etc. + */ + public Statistics getStatistics(); + + /** + * Clears certain channels of the current 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 + */ + public void setBackgroundColor(ColorRGBA color); + + /** + * Applies the given renderstate, making the neccessary + * GL calls so that the state is applied. + */ + public void applyRenderState(RenderState state); + + /** + * Set the range of the depth values for objects. + * @param start + * @param end + */ + public void setDepthRange(float start, float end); + + /** + * Called when a new frame has been rendered. + */ + public void onFrame(); + + /** + * @param transform The world transform to use. This changes + * the world matrix given in the shader. + */ + 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); + + /** + * @param shader Sets the shader to use for rendering, uploading it + * if neccessary. + */ + public void setShader(Shader shader); + + /** + * @param shader The shader to delete. This method also deletes + * the attached shader sources. + */ + public void deleteShader(Shader shader); + + /** + * Deletes the provided shader source. + * @param source + */ + public void deleteShaderSource(ShaderSource source); + + /** + * Copies contents from src to dst, scaling if neccessary. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst); + + /** + * Sets the framebuffer that will be drawn to. + */ + public void setFrameBuffer(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 + * @param byteBuf + */ + 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. + * @param tex + */ + 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 count meshes, with the geometry data supplied. + * The shader which is currently set with setShader 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 + * @param count + */ + public void renderMesh(Mesh mesh, int lod, int count); + + /** + * Called on restart() to reset all GL objects + */ + public void resetGLObjects(); + + /** + * Called when the display is restarted to delete + * all created GL objects. + */ + public void cleanup(); + + /** + * sets alpha to coverage + * @param value + */ + 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 000000000..ee507c91b --- /dev/null +++ b/engine/src/core/com/jme3/renderer/RendererException.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.renderer; + +public class RendererException extends RuntimeException { + 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 000000000..e31129364 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Statistics.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.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; + +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 shadersUsed = new HashSet(); + protected HashSet texturesUsed = new HashSet(); + protected HashSet fbosUsed = new HashSet(); + + 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)" }; + + } + + 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; + } + + public void onMeshDrawn(Mesh mesh, int lod){ + numObjects ++; + numTriangles += mesh.getTriangleCount(lod); + numVertices += mesh.getVertexCount(); + } + + public void onShaderUse(Shader shader, boolean wasSwitched){ + assert shader.id >= 1; + + if (!shadersUsed.contains(shader.id)) + shadersUsed.add(shader.id); + + if (wasSwitched) + numShaderSwitches++; + } + + public void onUniformSet(){ + numUniformsSet ++; + } + + public void onTextureUse(Image image, boolean wasSwitched){ + assert image.id >= 1; + + if (!texturesUsed.contains(image.id)) + texturesUsed.add(image.id); + + if (wasSwitched) + numTextureBinds ++; + } + + public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){ + if (fb != null){ + assert fb.id >= 1; + + if (!fbosUsed.contains(fb.id)) + fbosUsed.add(fb.id); + } + + if (wasSwitched) + numFboSwitches ++; + } + + public void clearFrame(){ + shadersUsed.clear(); + texturesUsed.clear(); + fbosUsed.clear(); + + numObjects = 0; + numTriangles = 0; + numVertices = 0; + numShaderSwitches = 0; + numTextureBinds = 0; + numFboSwitches = 0; + numUniformsSet = 0; + } + + public void onNewShader(){ + memoryShaders ++; + } + + public void onNewTexture(){ + memoryTextures ++; + } + + public void onNewFrameBuffer(){ + memoryFrameBuffers ++; + } + + public void onDeleteShader(){ + memoryShaders --; + } + + public void onDeleteTexture(){ + memoryTextures --; + } + + public void onDeleteFrameBuffer(){ + memoryFrameBuffers --; + } + + 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 000000000..a3f5e1a87 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/ViewPort.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.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; + +public class ViewPort { + + protected final String name; + protected final Camera cam; + protected final RenderQueue queue = new RenderQueue(); + protected final ArrayList sceneList = new ArrayList(); + protected final ArrayList processors = new ArrayList(); + protected FrameBuffer out = null; + + protected final ColorRGBA backColor = new ColorRGBA(0,0,0,0); + protected boolean clearEnabled = false; + protected boolean clearDepth = true, clearColor = true, clearStencil = true; + private boolean enabled = true; + + public ViewPort(String name, Camera cam) { + this.name = name; + this.cam = cam; + } + + public String getName() { + return name; + } + + public List getProcessors(){ + return processors; + } + + public void addProcessor(SceneProcessor processor){ + processors.add(processor); + } + + public void removeProcessor(SceneProcessor processor){ + processors.remove(processor); + processor.cleanup(); + } + + public boolean isClearEnabled() { + return clearEnabled; + } + + public boolean isClearDepth() { + return clearDepth; + } + + public void setClearDepth(boolean clearDepth) { + this.clearDepth = clearDepth; + } + + public boolean isClearColor() { + return clearColor; + } + + public void setClearColor(boolean clearColor) { + this.clearColor = clearColor; + } + + public boolean isClearStencil() { + return clearStencil; + } + + public void setClearStencil(boolean clearStencil) { + this.clearStencil = clearStencil; + } + + public void setClearEnabled(boolean clearEnabled) { + this.clearEnabled = clearEnabled; + } + + public void setClearFlags(boolean color, boolean depth, boolean stencil){ + this.clearEnabled = true; + this.clearColor = color; + this.clearDepth = depth; + this.clearStencil = stencil; + } + + public FrameBuffer getOutputFrameBuffer() { + return out; + } + + public void setOutputFrameBuffer(FrameBuffer out) { + this.out = out; + } + + public Camera getCamera() { + return cam; + } + + public RenderQueue getQueue() { + return queue; + } + + public void attachScene(Spatial scene){ + sceneList.add(scene); + } + + public void detachScene(Spatial scene){ + sceneList.remove(scene); + } + + public void clearScenes() { + sceneList.clear(); + } + + public List getScenes(){ + return sceneList; + } + + public void setBackgroundColor(ColorRGBA background){ + backColor.set(background); + } + + public ColorRGBA getBackgroundColor(){ + return backColor; + } + + public void setEnabled(boolean enable) { + this.enabled = enable; + } + + public boolean isEnabled() { + return enabled; + } + +} diff --git a/engine/src/core/com/jme3/renderer/layer/FrameBufferLayer.java b/engine/src/core/com/jme3/renderer/layer/FrameBufferLayer.java new file mode 100644 index 000000000..1ba79929b --- /dev/null +++ b/engine/src/core/com/jme3/renderer/layer/FrameBufferLayer.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.renderer.layer; + +import com.jme3.texture.FrameBuffer; + +/** + * Layer for handling framebuffers + */ +public interface FrameBufferLayer { + + /** + * Copies contents from src to dst, scaling if neccessary. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst); + + /** + * Sets the framebuffer that will be drawn to. + */ + public void setFrameBuffer(FrameBuffer fb); + + /** + * Initializes the framebuffer, creating it if neccessary and allocating + * requested renderbuffers. + */ + public void updateFrameBuffer(FrameBuffer fb); + + /** + * Deletes a framebuffer and all attached renderbuffers + */ + public void deleteFrameBuffer(FrameBuffer fb); + +} diff --git a/engine/src/core/com/jme3/renderer/layer/MeshLayer.java b/engine/src/core/com/jme3/renderer/layer/MeshLayer.java new file mode 100644 index 000000000..d713a894b --- /dev/null +++ b/engine/src/core/com/jme3/renderer/layer/MeshLayer.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 com.jme3.renderer.layer; + +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; + +/** + * Layer for handling meshes. + */ +public interface MeshLayer { + + /** + * Uploads the vertex buffer's data onto the GPU, assiging it an ID if + * needed. + */ + public void updateBufferData(VertexBuffer vb); + + /** + * Deletes a vertex buffer from the GPU. + * @param vb The vertex buffer to delete + */ + public void deleteBuffer(VertexBuffer vb); + + /** + * Sets the vertex attrib. This data is exposed in the shader depending + * on type, e.g Type.Position would be given as inPosition, Type.Tangent is + * given as inTangent, etc. + * + * @param vb + * @throws InvalidArgumentException If the given vertex buffer is an + * index buffer. + */ + public void setVertexAttrib(VertexBuffer vb); + + /** + * Draws the list of triangles given in the index buffer. + * Each triangle is composed of 3 indices to a vertex, the attribs of which + * are supplied using setVertexAttrib. + * The int variable gl_VertexID can be used to access the current + * vertex index inside the vertex shader. + * + * @param count The number of instances to draw + */ + public void drawTriangleList(VertexBuffer indexBuf, Mesh.Mode mode, int count, int vertCount); + + /** + * Clears all vertex attributes set with setVertexAttrib. + */ + public void clearVertexAttribs(); + + /** + * Renders count meshes, with the geometry data supplied. + * The shader which is currently set with setShader 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 + * @param count + */ + public void renderMesh(Mesh mesh, int count); + +} diff --git a/engine/src/core/com/jme3/renderer/layer/MiscLayer.java b/engine/src/core/com/jme3/renderer/layer/MiscLayer.java new file mode 100644 index 000000000..878484eab --- /dev/null +++ b/engine/src/core/com/jme3/renderer/layer/MiscLayer.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.renderer.layer; + +import com.jme3.renderer.*; +import com.jme3.math.ColorRGBA; +import java.util.Collection; + +/** + * Handles basic renderer functions. + */ +public interface MiscLayer { + + /** + * @return The capabilities of the renderer. + */ + public Collection getCaps(); + + /** + * Clears certain channels of the current 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 + */ + public void setBackgroundColor(ColorRGBA color); + +} diff --git a/engine/src/core/com/jme3/renderer/layer/ShaderLayer.java b/engine/src/core/com/jme3/renderer/layer/ShaderLayer.java new file mode 100644 index 000000000..5dcdcfb67 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/layer/ShaderLayer.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.renderer.layer; + +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; + +/** + * Renderer layer for handling shaders + */ +public interface ShaderLayer { + + /** + * Updates the shader source, creating an ID and registering + * with the object manager. + * @param source + */ + public void updateShaderSourceData(ShaderSource source); + + /** + * Uploads the shader source code and prepares it for use. + * @param shader + */ + public void updateShaderData(Shader shader); + + /** + * @param shader Sets the shader to use for rendering, uploading it + * if neccessary. + */ + public void setShader(Shader shader); + + /** + * @param shader The shader to delete. This method also deletes + * the attached shader sources. + */ + public void deleteShader(Shader shader); + + /** + * Deletes the provided shader source. + * @param source + */ + public void deleteShaderSource(ShaderSource source); + +} diff --git a/engine/src/core/com/jme3/renderer/layer/TextureLayer.java b/engine/src/core/com/jme3/renderer/layer/TextureLayer.java new file mode 100644 index 000000000..445461847 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/layer/TextureLayer.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.renderer.layer; + +import com.jme3.texture.Texture; + +/** + * Renderer layer for handling textures + */ +public interface TextureLayer { + + /** + * Prepares the texture for use and uploads its image data if neceessary. + */ + public void updateTextureData(Texture tex); + + /** + * Sets the texture to use for the given texture unit. + */ + public void setTexture(int unit, Texture tex); + + /** + * Clears all set texture units + * @see #setTexture + */ + public void clearTextureUnits(); + + /** + * Deletes a texture from the GPU. + * @param tex + */ + public void deleteTexture(Texture tex); + +} 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 000000000..bc938d163 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +public interface GeometryComparator extends Comparator { + 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 000000000..858b4f6b6 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/GeometryList.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.renderer.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import java.util.Arrays; + +/** + * This class is a special function list of Spatial objects for render + * queuing. + * + * @author Jack Lindamood + * @author Three Rings - better sorting alg. + */ +public class GeometryList { + + private static final int DEFAULT_SIZE = 32; + + private Geometry[] geometries; + private int size; + private GeometryComparator comparator; + + public GeometryList(GeometryComparator comparator) { + size = 0; + geometries = new Geometry[DEFAULT_SIZE]; + this.comparator = comparator; + } + + public void setCamera(Camera cam){ + this.comparator.setCamera(cam); + } + + public int size(){ + return size; + } + + public Geometry get(int index){ + return geometries[index]; + } + + /** + * Adds a spatial to the list. List size is doubled if there is no room. + * + * @param s + * The spatial 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 + } + 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 + Arrays.sort(geometries, 0, size, comparator); +// Arrays.sort(geometries, 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 000000000..b35a03c40 --- /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; + +/** + * GuiComparator 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 000000000..6463558e4 --- /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; + +/** + * NullComparator 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 000000000..0657554d3 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.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.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(); + + 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(); + 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) { + if (o1 == null || o2 == null) + return -1; + + 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 000000000..577a52b5b --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/RenderQueue.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.renderer.queue; + +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; + +public class RenderQueue { + + private GeometryList opaqueList; + private GeometryList guiList; + private GeometryList transparentList; + private GeometryList skyList; + private GeometryList shadowRecv; + private GeometryList shadowCast; + + public RenderQueue(){ + this.opaqueList = new GeometryList(new OpaqueComparator()); + this.guiList = new GeometryList(new GuiComparator()); + this.transparentList = new GeometryList(new TransparentComparator()); + this.skyList = new GeometryList(new NullComparator()); + this.shadowRecv = new GeometryList(new OpaqueComparator()); + this.shadowCast = new GeometryList(new OpaqueComparator()); + } + + public enum Bucket { + Gui, + Opaque, + Sky, + Transparent, + Inherit, + } + + public enum ShadowMode { + Off, + Cast, + Receive, + CastAndReceive, + Inherit + } + + 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); + } + } + + 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; + default: + throw new UnsupportedOperationException("Unknown bucket type: "+bucket); + } + } + + public GeometryList getShadowQueueContent(ShadowMode shadBucket){ + switch (shadBucket){ + case Cast: + return shadowCast; + case Receive: + return shadowRecv; + default: + return null; + } + } + + 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++){ + Spatial obj = list.get(i); + assert obj != null; + //if(obj.checkCulling(cam)){ + if (obj instanceof Geometry){ + Geometry g = (Geometry) obj; + rm.renderGeometry(g); + // make sure to reset queue distance + } + //} + if (obj != null) + 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; + } + } + + 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; + 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; + default: + throw new UnsupportedOperationException("Unsupported bucket type: "+bucket); + } + } + + public void clear(){ + opaqueList.clear(); + guiList.clear(); + transparentList.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 000000000..0821ebadf --- /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 000000000..7ea393e9b --- /dev/null +++ b/engine/src/core/com/jme3/scene/AssetLinkNode.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.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 java.io.IOException; +import java.util.ArrayList; +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; +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 assetLoaderKeys = new ArrayList(); + protected Map assetChildren = new HashMap(); + + 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 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
+ * If they are already attached, they will be reloaded. + * @param manager + */ + public void attachLinkedChildren(AssetManager manager) { + detachLinkedChildren(); + for (Iterator 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> set = assetChildren.entrySet(); + for (Iterator> it = set.iterator(); it.hasNext();) { + Entry 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) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList()); + for (Iterator 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, "Could not load linked child spatial: {0}", modelKey.getName()); + } + } + } + + @Override + public void write(JmeExporter e) throws IOException { + ArrayList childs = children; + children = new ArrayList(); + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null); + children = childs; + } +} 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 000000000..cccd64770 --- /dev/null +++ b/engine/src/core/com/jme3/scene/CameraNode.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 com.jme3.scene; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.control.CameraControl; +import com.jme3.scene.control.CameraControl.ControlDirection; + +/** + * This Node is a shorthand for using a CameraControl. + * + * @author Tim8Dev + */ +public class CameraNode extends Node { + + private CameraControl camControl; + + /** + * for IO purpose + */ + public CameraNode() { + } + + /** + * + * @param camera + * @deprecated Use the constructors that take a name + */ + @Deprecated + public CameraNode(Camera camera) { + this("defCamNodeName", camera); + } + + /** + * + * @param control + * @deprecated Use the constructors that take a name + */ + @Deprecated + public CameraNode(CameraControl control) { + this("defCamNodeName", control); + } + + 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 000000000..6276a2afe --- /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; + +/** + * CollisionData is an interface that can be used to + * do triangle-accurate collision between bounding volumes and rays. + * + * @author Kirill Vainer + */ +public interface CollisionData extends Savable { + 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 000000000..f64a34af5 --- /dev/null +++ b/engine/src/core/com/jme3/scene/Geometry.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.Queue; + +public class Geometry extends Spatial { + + /** + * The mesh contained herein + */ + 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(); + + /** + * Do not use this constructor. Serialization purposes only. + */ + public Geometry(){ + } + + /** + * Create a geometry node without any mesh data. + * @param name The name of this geometry + */ + public Geometry(String name){ + super(name); + } + + /** + * Create a geometry node with mesh data. + * + * @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; + } + + @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; + } + + public int getLodLevel(){ + return lodLevel; + } + + public int getVertexCount(){ + return mesh.getVertexCount(); + } + + public int getTriangleCount(){ + return mesh.getTriangleCount(); + } + + public void setMesh(Mesh mesh){ + if (mesh == null) + throw new NullPointerException(); + + this.mesh = mesh; + setBoundRefresh(); + } + + public Mesh getMesh(){ + return mesh; + } + + @Override + public void setMaterial(Material material){ + this.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(); + } + + /** + * updateWorldBound 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 com.jme.scene.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(); + + // geometry requires lights to be sorted + worldLights.sort(true); + } + + /** + * 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()); + + assert TempVars.get().lock(); + Matrix4f scaleMat = TempVars.get().tempMat4; + scaleMat.loadIdentity(); + scaleMat.scale(worldTransform.getScale()); + cachedWorldMat.multLocal(scaleMat); + assert TempVars.get().unlock(); + } + + /** + * @return 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. + */ + public Matrix4f getWorldMatrix(){ + return cachedWorldMat; + } + + @Override + public void setModelBound(BoundingVolume modelBound) { + this.worldBound = null; + mesh.setBound(modelBound); + 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 queue) { + } + + /** + * 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. + * @return + */ + @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.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. + * @return + */ + 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. + * @return + */ + @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); + 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 = (Material) ic.readSavable("material", null); + ignoreTransform = ic.readBoolean("ignoreTransform", false); + } + +} 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 000000000..be7f999cb --- /dev/null +++ b/engine/src/core/com/jme3/scene/Mesh.java @@ -0,0 +1,714 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.scene.mesh.IndexShortBuffer; +import com.jme3.scene.mesh.IndexIntBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.mesh.IndexByteBuffer; +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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; + +public class Mesh implements Savable, Cloneable { + + // TODO: Document this enum + public enum Mode { + Points, + Lines, + LineLoop, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Hybrid + } + +// private static final int BUFFERS_SIZE = VertexBuffer.Type.BoneIndex.ordinal() + 1; + + /** + * The bounding volume that contains the mesh entirely. + * By default a BoundingBox (AABB). + */ + private BoundingVolume meshBound = new BoundingBox(); + + private CollisionData collisionTree = null; + + private IntMap buffers = new IntMap(); + 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; + + public 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.vertexArrayID = -1; + if (elementLengths != null) + clone.elementLengths = elementLengths.clone(); + if (modeStart != null) + clone.modeStart = modeStart.clone(); + return clone; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + + public Mesh deepClone(){ + try{ + Mesh clone = (Mesh) super.clone(); + clone.meshBound = meshBound != null ? meshBound.clone() : null; + clone.collisionTree = collisionTree != null ? collisionTree : null; + clone.buffers = new IntMap(); + for (Entry ent : buffers){ + clone.buffers.put(ent.getKey(), ent.getValue().clone()); + } + clone.vertexArrayID = -1; + clone.vertCount = -1; + clone.elementCount = -1; + clone.maxNumWeights = -1; + clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; + clone.modeStart = modeStart != null ? modeStart.clone() : null; + return clone; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + + 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); + } + } + return clone; + } + + public void prepareForAnim(boolean swAnim){ + if (swAnim){ + // 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); + } + } + + 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; + } + + public VertexBuffer getLodLevel(int lod){ + return lodLevels[lod]; + } + + public int[] getElementLengths() { + return elementLengths; + } + + public void setElementLengths(int[] elementLengths) { + this.elementLengths = elementLengths; + } + + public int[] getModeStart() { + return modeStart; + } + + public void setModeStart(int[] modeStart) { + this.modeStart = modeStart; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + updateCounts(); + } + + public int getMaxNumWeights() { + return maxNumWeights; + } + + public void setMaxNumWeights(int maxNumWeights) { + this.maxNumWeights = maxNumWeights; + } + + public float getPointSize() { + return pointSize; + } + + public void setPointSize(float pointSize) { + this.pointSize = pointSize; + } + + public float getLineWidth() { + return lineWidth; + } + + public void setLineWidth(float lineWidth) { + this.lineWidth = lineWidth; + } + + /** + * Locks the mesh so it cannot be modified anymore, thus + * optimizing its data. + */ + public void setStatic() { + for (Entry entry : buffers){ + entry.getValue().setUsage(Usage.Static); + } + } + + /** + * Unlocks the mesh so it can be modified, this + * will un-optimize the data! + */ + public void setDynamic() { + for (Entry entry : buffers){ + entry.getValue().setUsage(Usage.Dynamic); + } + } + + public void setStreamed(){ + for (Entry entry : buffers){ + entry.getValue().setUsage(Usage.Stream); + } + } + + public void setInterleaved(){ + ArrayList vbs = new ArrayList(); + for (Entry entry : buffers){ + vbs.add(entry.getValue()); + } +// ArrayList vbs = new ArrayList(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); + + 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; + } + } + } + + int offset = 0; + for (VertexBuffer vb : vbs){ + vb.setOffset(offset); + vb.setStride(stride); + + // discard old buffer + 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(); + } + } + + 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); + } + } + + 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!"); + } + } + + public int getTriangleCount(){ + return elementCount; + } + + public int getVertexCount(){ + return vertCount; + } + + /** + * + * @param count + * @deprecated Use {@link Mesh#updateCounts() } to update the counts after + * updating the buffers. + */ + @Deprecated + public void setTriangleCount(int count){ + throw new UnsupportedOperationException("Deprecated"); + } + + /** + * + * @param count + * @deprecated Use {@link Mesh#updateCounts() } to update the counts after + * updating the buffers. + */ + @Deprecated + public void setVertexCount(int count){ + throw new UnsupportedOperationException("Deprecated"); + } + + public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){ + VertexBuffer pb = getBuffer(Type.Position); + VertexBuffer ib = getBuffer(Type.Index); + + if (pb.getFormat() == Format.Float){ + FloatBuffer fpb = (FloatBuffer) pb.getData(); + + if (ib.getFormat() == Format.UnsignedShort){ + // accepted format for buffers + ShortBuffer sib = (ShortBuffer) ib.getData(); + + // aquire triangle's vertex indices + int vertIndex = index * 3; + int vert1 = sib.get(vertIndex); + int vert2 = sib.get(vertIndex+1); + int vert3 = sib.get(vertIndex+2); + + BufferUtils.populateFromBuffer(v1, fpb, vert1); + BufferUtils.populateFromBuffer(v2, fpb, vert2); + BufferUtils.populateFromBuffer(v3, fpb, vert3); + } + } + } + + public void getTriangle(int index, Triangle tri){ + getTriangle(index, tri.get1(), tri.get2(), tri.get3()); + tri.setIndex(index); + } + + public void getTriangle(int index, int[] indices){ + VertexBuffer ib = getBuffer(Type.Index); + if (ib.getFormat() == Format.UnsignedShort){ + // accepted format for buffers + ShortBuffer sib = (ShortBuffer) ib.getData(); + + // aquire triangle's vertex indices + int vertIndex = index * 3; + indices[0] = sib.get(vertIndex); + indices[1] = sib.get(vertIndex+1); + indices[2] = sib.get(vertIndex+2); + } + } + + public int getId(){ + return vertexArrayID; + } + + public void setId(int id){ + if (vertexArrayID != -1) + throw new IllegalStateException("ID has already been set."); + + vertexArrayID = id; + } + + public void createCollisionData(){ + BIHTree tree = new BIHTree(this); + tree.construct(); + collisionTree = tree; + } + + public int collideWith(Collidable other, + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results){ + + if (collisionTree == null){ + createCollisionData(); + } + + return collisionTree.collideWith(other, worldMatrix, worldBound, results); + } + +// public void setLodData(ShortBuffer[] lodData){ +// this.lodData = lodData; +// } + + 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); + }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); + 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); + 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); + 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); + updateCounts(); + } + + public void clearBuffer(VertexBuffer.Type type){ + buffers.remove(type.ordinal()); + updateCounts(); + } + + public void setBuffer(Type type, int components, short[] buf){ + setBuffer(type, components, BufferUtils.createShortBuffer(buf)); + } + + public VertexBuffer getBuffer(Type type){ + return buffers.get(type.ordinal()); + } + + public FloatBuffer getFloatBuffer(Type type) { + VertexBuffer vb = getBuffer(type); + if (vb == null) + return null; + + return (FloatBuffer) vb.getData(); + } + + public ShortBuffer getShortBuffer(Type type) { + VertexBuffer vb = getBuffer(type); + if (vb == null) + return null; + + return (ShortBuffer) vb.getData(); + } + + 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()); + } + } + + 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); + } + + public void updateBound(){ + VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position); + if (meshBound != null && posBuf != null){ + meshBound.computeFromPoints((FloatBuffer)posBuf.getData()); + } + } + + public BoundingVolume getBound() { + return meshBound; + } + + public void setBound(BoundingVolume modelBound) { + meshBound = modelBound; + } + + public IntMap getBuffers(){ + return buffers; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + +// HashMap map = new HashMap(); +// for (Entry 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) in.readIntSavableMap("buffers", null); + Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null); + if(lodLevelsSavable!=null){ + lodLevels = new VertexBuffer[lodLevelsSavable.length]; + for (int i = 0; i < lodLevels.length; i++){ + lodLevels[i] = (VertexBuffer) lodLevelsSavable[i]; + } + } + } + +} 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 000000000..5a72496f5 --- /dev/null +++ b/engine/src/core/com/jme3/scene/Node.java @@ -0,0 +1,649 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 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; + + +/** + * Node 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 ArrayList children = new ArrayList(1); + + /** + * Default constructor. + */ + public Node() { + } + + /** + * Constructor instantiates a new Node 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); + } + + /** + * + * getQuantity 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){ + if ((child.refreshFlags & RF_TRANSFORM) != 0) + return; + + child.setTransformRefresh(); + } + } + + @Override + protected void setLightListRefresh(){ + super.setLightListRefresh(); + for (Spatial child : children){ + if ((child.refreshFlags & RF_LIGHTLIST) != 0) + return; + + 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 (int i = 0, cSize = children.size(); i < cSize; i++) { + Spatial child = children.get(i); + // 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; + } + + /** + * updateLogicalState updates logic state. Should + * be overriden by subclasses desiring this functionality. + * @param tpf Time per frame + */ + @Override + public void updateLogicalState(float tpf){ + super.updateLogicalState(tpf); + for (int i = 0, cSize = children.size(); i < cSize; i++) { + Spatial child = children.get(i); + child.updateLogicalState(tpf); + + // added this line so that nodes removed by Controls + // don't cause IndexOutOfBoundsExceptions + // FIXME: This is sometimes unreliable, a more + // robust solution is needed + cSize = children.size(); + } + } + + /** + * updateGeometricState updates all the geometry information + * for the node. + * + * @param time + * the frame time. + * @param initiator + * true if this node started the update process. + */ + @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(); + } + + // 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 (int i = 0, cSize = children.size(); i < cSize; i++) { + Spatial child = children.get(i); + child.updateGeometricState(); + } + + if ((refreshFlags & RF_BOUND) != 0){ + updateWorldBound(); + } + + assert refreshFlags == 0; + } + + /** + * getTriangleCount 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; + } + + /** + * getVertexCount 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; + } + + /** + * + * attachChild attaches a child to this node. This node + * becomes the child's parent. The current number of children maintained is + * returned. + *
+ * 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(); + } + + /** + * + * attachChildAt attaches a child to this node at an index. This node + * becomes the child's parent. The current number of children maintained is + * returned. + *
+ * 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(); + } + + /** + * detachChild removes a given child from the node's list. + * This child will no longe 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; + } + + /** + * detachChild 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; + } + + /** + * + * detachChildAt 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.info("Child removed."); + + // 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; + } + + /** + * + * detachAllChildren removes all children attached to this + * node. + */ + public void detachAllChildren() { + for ( int i = children.size() - 1; i >= 0; i-- ) { + detachChildAt(i); + } + logger.info("All children removed."); + } + + public int getChildIndex(Spatial sp) { + return children.indexOf(sp); + } + + /** + * More efficient than e.g detaching and attaching as no updates are needed. + * @param index1 + * @param index2 + */ + 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); + } + + /** + * + * getChild 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); + } + + /** + * getChild 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 (int x = 0, cSize = getQuantity(); x < cSize; x++) { + Spatial child = children.get(x); + 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 (int i = 0, max = getQuantity(); i < max; i++) { + Spatial child = children.get(i); + if (child instanceof Node && ((Node) child).hasChild(spat)) + return true; + } + + return false; + } + + /** + * Returns all children to this node. + * + * @return a list containing all children to this node + */ + public List getChildren() { + return children; + } + + public void childChange(Geometry geometry, int index1, int index2) { + //just pass to parent + if(parent != null) { + parent.childChange(geometry, index1, index2); + } + } + + @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 (int i = 0; i < children.size(); i++){ + children.get(i).setLodLevel(lod); + } + } + + public int collideWith(Collidable other, CollisionResults results){ + int total = 0; + for (Spatial child : children){ + total += child.collideWith(other, results); + } + return total; + } + + + /** + * Returns flat list of Spatials implementing the specified class AND + * with name matching the specified pattern. + *

+ * Note that we are matching 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. + *

+ * By design, it is always safe to code loops like:

+     *     for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
+     * 
+ *

+ * "Descendants" does not include self, per the definition of the word. + * To test for descendants AND self, you must do a + * node.matches(aClass, aRegex) + + * node.descendantMatches(aClass, aRegex). + *

+ * + * @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(Class, String) + */ + @SuppressWarnings("unchecked") + public List descendantMatches( + Class spatialSubclass, String nameRegex) { + List newList = new ArrayList(); + 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(Class, String) + */ + public List descendantMatches( + Class spatialSubclass) { + return descendantMatches(spatialSubclass, null); + } + + /** + * Convenience wrapper. + * + * @see #descendantMatches(Class, String) + */ + public List descendantMatches(String nameRegex) { + return descendantMatches(null, nameRegex); + } + + @Override + public Node clone(boolean cloneMaterials){ + Node nodeClone = (Node) super.clone(cloneMaterials); +// nodeClone.children = new ArrayList(); +// 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 ArrayList(); + 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(children, "children", null); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + children = e.getCapsule(this).readSavableArrayList("children", null); + + // go through children and set parent to this node + if (children != null) { + for (int x = 0, cSize = children.size(); x < cSize; x++) { + Spatial child = children.get(x); + child.parent = this; + } + } + } + + @Override + public void setModelBound(BoundingVolume modelBound) { + if(children != null) { + for(int i = 0, max = children.size(); i < max; i++) { + children.get(i).setModelBound(modelBound != null ? modelBound.clone(null) : null); + } + } + } + + @Override + public void updateModelBound() { + if(children != null) { + for(int i = 0, max = children.size(); i < max; i++) { + children.get(i).updateModelBound(); + } + } + } + + @Override + public void depthFirstTraversal(SceneGraphVisitor visitor) { + for(int i = 0, max = children.size(); i < max; i++) { + children.get(i).depthFirstTraversal(visitor); + } + visitor.visit(this); + } + + @Override + protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue 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 000000000..e9099ce65 --- /dev/null +++ b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java @@ -0,0 +1,9 @@ +package com.jme3.scene; + +/** + * Interface to traverse and visit scene graph + * by calling Spatial.depthFirstTraversal() or Spatial.breadthFirstTraversal(). + */ +public interface SceneGraphVisitor { + 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 000000000..3644973e0 --- /dev/null +++ b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java @@ -0,0 +1,22 @@ +package com.jme3.scene; + +/** + * Traverse and visit Geometry/Node + * by calling Spatial.depthFirstTraversal() or Spatial.breadthFirstTraversal(). + * + */ +public class SceneGraphVisitorAdapter implements SceneGraphVisitor { + + public void visit(Geometry geom) {} + + 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/Spatial.java b/engine/src/core/com/jme3/scene/Spatial.java new file mode 100644 index 000000000..af4cd4493 --- /dev/null +++ b/engine/src/core/com/jme3/scene/Spatial.java @@ -0,0 +1,1420 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.material.Material; +import com.jme3.math.Matrix3f; +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.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +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.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Logger; + + +/** + * Spatial 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 nodes, such as Node and + * Geometry are subclasses of Spatial. + * + * @author Mark Powell + * @author Joshua Slack + * @version $Revision: 4075 $, $Data$ + */ +public abstract class Spatial implements Savable, Cloneable, Collidable { + + private static final Logger logger = Logger.getLogger(Spatial.class.getName()); + + public enum CullHint { + /** + * Do whatever our parent does. If no parent, we'll default to dynamic. + */ + Inherit, + + /** + * Do not draw if we are not at least partially within the view frustum + * of the renderer's camera. + */ + Dynamic, + + /** + * Always cull this from view. + */ + Always, + + /** + * Never cull this from view. Note that we will still get culled if our + * parent is culled. + */ + 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; + + /** + * 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 ArrayList controls = new ArrayList(1); + + protected HashMap userData = null; + + /** + * 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; + + /** + * Default Constructor. + */ + public Spatial() { + localTransform = new Transform(); + worldTransform = new Transform(); + + localLights = new LightList(this); + worldLights = new LightList(this); + + refreshFlags |= RF_BOUND; + } + + /** + * Constructor instantiates a new Spatial object setting the + * rotation, translation and scale value to defaults. + * + * @param name + * the name of the scene element. This is required for + * identification and comparision purposes. + */ + public Spatial(String name) { + this(); + this.name = name; + } + + /** + * 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; + } + } + + /** + * checkCulling checks the spatial with the camera to see if it + * should be culled. + *

+ * 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" + + "Make sure scene graph state was not changed after\n" + + " rootNode.updateGeometricState() call. \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) { + int state = cam.getPlaneState(); + + frustrumIntersects = cam.contains(getWorldBound()); + + cam.setPlaneState(state); + } + + 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; + } + + public LightList getLocalLightList(){ + return localLights; + } + + public LightList getWorldLightList() { + return worldLights; + } + + /** + * getWorldRotation retrieves the absolute rotation of the + * Spatial. + * + * @return the Spatial's world rotation matrix. + */ + public Quaternion getWorldRotation() { + checkDoTransformUpdate(); + return worldTransform.getRotation(); + } + + /** + * getWorldTranslation retrieves the absolute translation of + * the spatial. + * + * @return the world's tranlsation vector. + */ + public Vector3f getWorldTranslation() { + checkDoTransformUpdate(); + return worldTransform.getTranslation(); + } + + /** + * getWorldScale retrieves the absolute scale factor of the + * spatial. + * + * @return the world's scale factor. + */ + public Vector3f getWorldScale() { + checkDoTransformUpdate(); + return worldTransform.getScale(); + } + + /** + * getWorldTransform retrieves the world transformation + * of the spatial. + * + * @return the world transform. + */ + public Transform getWorldTransform(){ + checkDoTransformUpdate(); + return worldTransform; + } + + /** + * rotateUpTo is a util function that alters the + * localrotation 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(); + assert vars.lock(); + + 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); + + assert vars.unlock(); + + setTransformRefresh(); + } + + /** + * lookAt is a convienence 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} this method takes a world position to + * look at 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(); + assert vars.lock(); + Vector3f compVecA = vars.vect4; + assert vars.unlock(); + + compVecA.set(position).subtractLocal(worldTranslation); + getLocalRotation().lookAt(compVecA, upVector); + + setTransformRefresh(); + } + + /** + * Should be overriden 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; + } + } + + void checkDoTransformUpdate(){ + if ( (refreshFlags & RF_TRANSFORM) == 0 ) + return; + + if (parent == null){ + worldTransform.set(localTransform); + refreshFlags &= ~RF_TRANSFORM; + }else{ + TempVars vars = TempVars.get(); + assert vars.lock(); + + 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++; + } + + assert vars.unlock(); + + 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(); + } + } + } + + 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.size() == 0) + return; + + for (int i = 0; i < controls.size(); i++){ + controls.get(i).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.size() == 0) + return; + + for (int i = 0; i < controls.size(); i++){ + controls.get(i).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 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 getControl(Class controlType){ + for (int i = 0; i < controls.size(); i++){ + if (controlType.isAssignableFrom(controls.get(i).getClass())){ + return (T) controls.get(i); + } + } + 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(); + } + + + /** + * updateLogicalState calls the update() 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); + } + + /** + * updateGeometricState 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) { + 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) { + return worldTransform.transformInverseVector(in, store); + } + + /** + * getParent retrieve's 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. + * setParent sets the parent of this node. + * + * @param parent + * the parent of this node. + */ + protected void setParent(Node parent) { + this.parent = parent; + } + + /** + * removeFromParent 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); + } + } + + /** + * getLocalRotation retrieves the local rotation of this + * node. + * + * @return the local rotation of this node. + */ + public Quaternion getLocalRotation() { + return localTransform.getRotation(); + } + + /** + * setLocalRotation sets the local rotation of this node. + * + * @param rotation + * the new local rotation. + */ + public void setLocalRotation(Matrix3f rotation) { + localTransform.getRotation().fromRotationMatrix(rotation); + this.worldTransform.setRotation(this.localTransform.getRotation()); + setTransformRefresh(); + } + + /** + * setLocalRotation sets the local rotation of this node, + * using a quaterion to build the matrix. + * + * @param quaternion + * the quaternion that defines the matrix. + */ + public void setLocalRotation(Quaternion quaternion) { + localTransform.setRotation(quaternion); + this.worldTransform.setRotation(this.localTransform.getRotation()); + setTransformRefresh(); + } + + /** + * getLocalScale retrieves the local scale of this node. + * + * @return the local scale of this node. + */ + public Vector3f getLocalScale() { + return localTransform.getScale(); + } + + /** + * setLocalScale 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); + worldTransform.setScale(localTransform.getScale()); + setTransformRefresh(); + } + + /** + * setLocalScale sets the local scale of this node. + * + * @param localScale + * the new local scale + */ + public void setLocalScale(float x, float y, float z) { + localTransform.setScale(x, y, z); + worldTransform.setScale(localTransform.getScale()); + setTransformRefresh(); + } + + /** + * setLocalScale sets the local scale of this node. + * + * @param localScale + * the new local scale. + */ + public void setLocalScale(Vector3f localScale) { + localTransform.setScale(localScale); + worldTransform.setScale(localTransform.getScale()); + setTransformRefresh(); + } + + /** + * getLocalTranslation retrieves the local translation of + * this node. + * + * @return the local translation of this node. + */ + public Vector3f getLocalTranslation() { + return localTransform.getTranslation(); + } + + /** + * setLocalTranslation sets the local translation of this + * spatial. + * + * @param localTranslation + * the local translation of this spatial. + */ + public void setLocalTranslation(Vector3f localTranslation) { + this.localTransform.setTranslation(localTranslation); + this.worldTransform.setTranslation(this.localTransform.getTranslation()); + setTransformRefresh(); + } + + /** + * setLocalTranslation sets the local translation of this + * spatial. + */ + public void setLocalTranslation(float x, float y, float z) { + this.localTransform.setTranslation(x,y,z); + this.worldTransform.setTranslation(this.localTransform.getTranslation()); + setTransformRefresh(); + } + + /** + * setLocalTransform sets the local transform of this + * spatial. + */ + public void setLocalTransform(Transform t) { + this.localTransform.set(t); + setTransformRefresh(); + } + + /** + * getLocalTransform 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){ + } + + /** + * addLight 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(); + } + + /** + * removeLight 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 this. + */ + public Spatial move(float x, float y, float z){ + this.localTransform.getTranslation().addLocal(x, y, z); + this.worldTransform.setTranslation(this.localTransform.getTranslation()); + setTransformRefresh(); + + return this; + } + + /** + * Translates the spatial by the given translation vector. + * + * @return The spatial on which this method is called, e.g this. + */ + public Spatial move(Vector3f offset){ + this.localTransform.getTranslation().addLocal(offset); + this.worldTransform.setTranslation(this.localTransform.getTranslation()); + setTransformRefresh(); + + return this; + } + + /** + * Scales the spatial by the given value + * + * @return The spatial on which this method is called, e.g this. + */ + 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 this. + */ + public Spatial scale(float x, float y, float z){ + this.localTransform.getScale().multLocal(x,y,z); + this.worldTransform.setScale(this.localTransform.getScale()); + setTransformRefresh(); + + return this; + } + + /** + * Rotates the spatial by the given rotation. + * + * @return The spatial on which this method is called, e.g this. + */ + public Spatial rotate(Quaternion rot){ + this.localTransform.getRotation().multLocal(rot); + this.worldTransform.setRotation(this.localTransform.getRotation()); + 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 this. + */ + public Spatial rotate(float yaw, float roll, float pitch){ + assert TempVars.get().lock(); + Quaternion q = TempVars.get().quat1; + q.fromAngles(yaw, roll, pitch); + rotate(q); + assert TempVars.get().unlock(); + + return this; + } + + /** + * Centers the spatial in the origin of the world bound. + * @return The spatial on which this method is called, e.g this. + */ + 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; + } + + + /** + * 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){ + } + + /** + * updateModelBound recalculates the bounding object for this + * Spatial. + */ + public abstract void updateModelBound(); + + /** + * setModelBound 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); + + clone.worldTransform = worldTransform.clone(); + clone.localTransform = localTransform.clone(); + + if (clone instanceof Node){ + Node node = (Node) this; + Node nodeClone = (Node) clone; + nodeClone.children = new ArrayList(); + 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 ArrayList(); + for (int i = 0; i < controls.size(); i++){ + clone.controls.add( controls.get(i).cloneForSpatial(clone) ); + } + + if (userData != null){ + clone.userData = (HashMap) 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(); + + if (data instanceof Savable){ + userData.put(key, (Savable) data); + }else{ + userData.put(key, new UserData(UserData.getObjectType(data), data)); + } + } + + public Object getUserData(String key){ + if (userData == null) + return null; + + Savable s = userData.get(key); + if (s instanceof UserData){ + return ((UserData)s).getValue(); + }else{ + return s; + } + } + + public Collection getUserDataKeys(){ + if (userData != null) + return userData.keySet(); + + return Collections.EMPTY_SET; + } + + /** + * Note that we are matching 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 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(queueBucket, "queue", RenderQueue.Bucket.Inherit); + capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit); + capsule.write(localTransform, "transform", Transform.Identity); + capsule.write(localLights, "lights", null); + capsule.writeSavableArrayList(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); + 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); + + controls = ic.readSavableArrayList("controlsList", null); + userData = (HashMap) ic.readStringSavableMap("user_data", null); + } + + /** + * getWorldBound retrieves the world bound at this node + * level. + * + * @return the world bound at this level. + */ + public BoundingVolume getWorldBound() { + checkDoBoundUpdate(); + return worldBound; + } + + /** + * setCullHint sets how scene culling should work on this + * spatial during drawing. CullHint.Dynamic: Determine via the defined + * Camera planes whether or not this Spatial should be culled. + * CullHint.Always: Always throw away this object and any children during + * draw commands. CullHint.Never: Never throw away this object (always draw + * it) CullHint.Inherit: Look for a non-inherit parent and use its cull + * mode. 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; + } + + /** + * @return the cullmode set on this Spatial + */ + public CullHint getLocalCullHint() { + return cullHint; + } + + /** + * setQueueBucket determines at what phase of the + * rendering proces this Spatial will rendered. There are 4 different + * phases: Bucket.Opaque - 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 transparant + * ones, as it could give a nice performance boost to your application. + * Bucket.Transparent - This is the mode you should use for object with + * transparancy 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 tranparant parts of the newly drawn + * object. Bucket.Gui - This is a special mode, for drawing 2D object + * without prespective (such as GUI or HUD parts) Lastly, there is a special + * mode, Bucket.Inherit, that will ensure that this spatial uses the same + * mode as the parent Node does. + * + * @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 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. (See texture renderer code + * for example.) + * + * @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
+ * 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 queue = new LinkedList(); + queue.add(this); + + while (!queue.isEmpty()) { + Spatial s = queue.poll(); + visitor.visit(s); + s.breadthFirstTraversal(visitor, queue); + } + } + + protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue 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 000000000..9d37212ec --- /dev/null +++ b/engine/src/core/com/jme3/scene/UserData.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 com.jme3.scene; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +public final class UserData implements Savable { + + protected byte type; + protected Object value; + + public UserData() { + } + + 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 000000000..13696fa92 --- /dev/null +++ b/engine/src/core/com/jme3/scene/VertexBuffer.java @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import com.jme3.renderer.GLObject; +import com.jme3.renderer.Renderer; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +/** + * A VertexBuffer contains a particular type of geometry + * data used by {@link Mesh}es. Every VertexBuffer set on a Mesh + * is sent as an attribute to the vertex shader to be processed. + */ +public class VertexBuffer extends GLObject 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. + */ + Size, + + /** + * Normal vector, normalized. + */ + Normal, + + /** + * Texture coordinate + */ + TexCoord, + + /** + * Color and Alpha (4 floats) + */ + Color, + + /** + * Tangent vector, normalized. + */ + Tangent, + + /** + * Binormal vector, normalized. + */ + Binormal, + + /** + * Specifies the source data for various vertex buffers + * when interleaving is used. + */ + InterleavedData, + + /** + * Do not use. + */ + @Deprecated + MiscAttrib, + + /** + * Specifies the index buffer, must contain integer data. + */ + Index, + + /** + * Inital vertex position, used with animation + */ + BindPosePosition, + + /** + * Inital vertex normals, used with animation + */ + BindPoseNormal, + + /** + * Bone weights, used with animation + */ + BoneWeight, + + /** + * Bone indices, used with animation + */ + BoneIndex, + + /** + * Texture coordinate #2 + */ + TexCoord2; + } + + /** + * 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 not sent to GPU at all. It is only + * used by the CPU. + */ + CpuOnly; + } + + public static enum Format { + // Floating point formats + Half(2), + Float(4), + Double(8), + + // Integer formats + Byte(1), + UnsignedByte(1), + Short(2), + UnsignedShort(2), + Int(4), + UnsignedInt(4); + + private int componentSize = 0; + + Format(int componentSize){ + this.componentSize = componentSize; + } + + /** + * @return Size in bytes of this data type. + */ + public int getComponentSize(){ + return componentSize; + } + } + + protected int offset = 0; + protected int stride = 0; + protected int components = 0; + + /** + * derived from components * format.getComponentSize() + */ + protected transient int componentsLength = 0; + protected Buffer data = null; + protected transient ByteBuffer mappedData; + 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(GLObject.Type.VertexBuffer); + this.bufType = type; + } + + /** + * Do not use this constructor. Serialization purposes only. + */ + public VertexBuffer(){ + super(GLObject.Type.VertexBuffer); + } + + protected VertexBuffer(int id){ + super(GLObject.Type.VertexBuffer, id); + } + + /** + * @return The offset (in bytes) from the start of the buffer + * after which the data is sent to the GPU. + */ + 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. 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. + */ + public int getStride() { + return stride; + } + + /** + * @param stride The stride (in bytes) for the data. 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. + */ + public void setStride(int stride) { + this.stride = stride; + } + + /** + * @return A native buffer, in the specified {@link Format format}. + */ + public Buffer getData(){ + return data; + } + + public ByteBuffer getMappedData() { + return mappedData; + } + + public void setMappedData(ByteBuffer mappedData) { + this.mappedData = mappedData; + } + + /** + * @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 VertexBuffer. 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."); + + this.data = data; + this.components = components; + this.usage = usage; + this.format = format; + this.componentsLength = components * format.getComponentSize(); + 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 + } + + // will force renderer to call glBufferData again + if (this.data.capacity() != data.capacity()){ + dataSizeChanged = true; + } + this.data = data; + setUpdateNeeded(); + } + + 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 + */ + 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; + } + + 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); + } + } + + public Object getElementComponent(int elementIndex, int componentIndex){ + 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; + return bin.get(inPos + elementPos); + case Short: + case UnsignedShort: + ShortBuffer sin = (ShortBuffer) data; + return sin.get(inPos + elementPos); + case Int: + case UnsignedInt: + IntBuffer iin = (IntBuffer) data; + return iin.get(inPos + elementPos); + case Float: + FloatBuffer fin = (FloatBuffer) data; + return fin.get(inPos + elementPos); + default: + throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + } + } + + /** + * Copies a single element of data from this VertexBuffer + * to the given output VertexBuffer. + * + * @param inIndex + * @param outVb + * @param outIndex + */ + public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){ + 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; + } + + data.clear(); + outVb.data.clear(); + + switch (format){ + case Byte: + case UnsignedByte: + case Half: + ByteBuffer bin = (ByteBuffer) data; + ByteBuffer bout = (ByteBuffer) outVb.data; + bin.position(inPos).limit(inPos + elementSz); + bout.position(outPos).limit(outPos + elementSz); + bout.put(bin); + break; + case Short: + case UnsignedShort: + ShortBuffer sin = (ShortBuffer) data; + ShortBuffer sout = (ShortBuffer) outVb.data; + sin.position(inPos).limit(inPos + elementSz); + sout.position(outPos).limit(outPos + elementSz); + sout.put(sin); + break; + case Int: + case UnsignedInt: + IntBuffer iin = (IntBuffer) data; + IntBuffer iout = (IntBuffer) outVb.data; + iin.position(inPos).limit(inPos + elementSz); + iout.position(outPos).limit(outPos + elementSz); + iout.put(iin); + break; + case Float: + FloatBuffer fin = (FloatBuffer) data; + FloatBuffer fout = (FloatBuffer) outVb.data; + fin.position(inPos).limit(inPos + elementSz); + fout.position(outPos).limit(outPos + elementSz); + fout.put(fin); + break; + default: + throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + } + + data.clear(); + 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. + * + * @param format + * @param components + * @param numElements + * @return + */ + 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); + } + } + + public VertexBuffer clone(){ + // NOTE: Superclass GLObject automatically creates shallow clone + // e.g re-use ID. + VertexBuffer vb = (VertexBuffer) super.clone(); + if (data != null) + vb.updateData(BufferUtils.clone(data)); + + return vb; + } + + public VertexBuffer clone(Type overrideType){ + VertexBuffer vb = new VertexBuffer(overrideType); + vb.components = components; + vb.componentsLength = componentsLength; + vb.data = BufferUtils.clone(data); + 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(Renderer r) { + r.deleteBuffer(this); + } + + @Override + public GLObject 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(); + switch (format){ + case Float: + oc.write((FloatBuffer) data, dataName, null); + break; + case Short: + case UnsignedShort: + oc.write((ShortBuffer) data, dataName, null); + break; + case UnsignedByte: + case Byte: + case Half: + oc.write((ByteBuffer) data, dataName, null); + break; + case Int: + case UnsignedInt: + oc.write((IntBuffer) data, 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 000000000..20117ccba --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/AbstractControl.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.scene.control; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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; + + /** + * @param spatial + * @deprecated The spatial parameter is passed in {@link AbstractControl#setSpatial(com.jme3.scene.Spatial) } + * automatically. + */ + @Deprecated + public AbstractControl(Spatial spatial){ + this.spatial = spatial; + } + + public AbstractControl(){ + } + + public void setSpatial(Spatial spatial) { + this.spatial = 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 000000000..47e7c5f78 --- /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; + +/** + * AreaUtils 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 000000000..a080f766c --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/BillboardControl.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 com.jme3.scene.control; + +import java.io.IOException; + +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; + +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); + } + + /** + * 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); + spatial.updateGeometricState(); + } + + /** + * 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.normalize(); + } + spatial.setLocalRotation(rot); + spatial.updateGeometricState(); + } + + /** + * 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.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); + spatial.updateGeometricState(); + } + + /** + * 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 000000000..0031b2127 --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/CameraControl.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.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 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; + } + + /** + * @param spatial + * @param camera + * @param controlDir + * @deprecated Use the constructor that doesn't take a spatial argument + */ + public CameraControl(Spatial spatial, Camera camera, ControlDirection controlDir) { + super(spatial); + this.camera = camera; + this.controlDir = controlDir; + } + + /** + * @param spatial The spatial to be synced. + * @param camera The Camera to be synced. + * @deprecated Use the constructor that doesn't take a spatial argument + */ + @Deprecated + public CameraControl(Spatial spatial, Camera camera) { + super(spatial); + this.camera = camera; + } + + 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: + Vector3f vecDiff = camera.getLocation().subtract(spatial.getWorldTranslation()); + spatial.getLocalTranslation().addLocal(vecDiff); + + // Rotation: + Quaternion worldDiff = camera.getRotation().subtract(spatial.getWorldRotation()); + spatial.getLocalRotation().addLocal(worldDiff); + 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 000000000..c54fb34c9 --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/Control.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.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. + * + * @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 + */ + 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/ControlType.java b/engine/src/core/com/jme3/scene/control/ControlType.java new file mode 100644 index 000000000..1f41529f8 --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/ControlType.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.scene.control; + +/** + * The type of control. + * + * @author Kirill Vainer. + */ +public enum ControlType { + + /** + * Manages the level of detail for the model. + */ + LevelOfDetail, + + /** + * Provides methods to manipulate the skeleton and bones. + */ + BoneControl, + + /** + * Handles the bone animation and skeleton updates. + */ + BoneAnimation, + + /** + * Handles attachments to bones + */ + Attachment, + + /** + * Handles vertex/morph animation. + */ + VertexAnimation, + + /** + * Handles poses or morph keys. + */ + Pose, + + /** + * Handles particle updates + */ + Particle + +} 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 000000000..ce0bf552d --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/LodControl.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.scene.control; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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; + + /** + * + * @param geom + * @deprecated Use {@link LodControl#LodControl() } + */ + @Deprecated + public LodControl(Geometry geom){ + } + + public LodControl(){ + } + + public float getDistTolerance() { + return distTolerance; + } + + public void setDistTolerance(float distTolerance) { + this.distTolerance = distTolerance; + } + + public float getTrisPerPixel() { + return trisPerPixel; + } + + 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/debug/Arrow.java b/engine/src/core/com/jme3/scene/debug/Arrow.java new file mode 100644 index 000000000..fcd76fcac --- /dev/null +++ b/engine/src/core/com/jme3/scene/debug/Arrow.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.scene.debug; + +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import java.nio.FloatBuffer; + +public class Arrow extends Mesh { + Quaternion tempQuat = new Quaternion(); + 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 + }; + + public Arrow() { + } + + public Arrow(Vector3f extent) { + float len = extent.length(); + Vector3f dir = extent.normalize(); + + tempQuat.lookAt(dir, Vector3f.UNIT_Y); + tempQuat.normalize(); + + 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(); + } + + public void setArrowExtent(Vector3f extent) { + float len = extent.length(); +// Vector3f dir = extent.normalize(); + + tempQuat.lookAt(extent, Vector3f.UNIT_Y); + tempQuat.normalize(); + + FloatBuffer buffer = getFloatBuffer(Type.Position); + 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); + } + + 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 000000000..3cf2181e0 --- /dev/null +++ b/engine/src/core/com/jme3/scene/debug/Grid.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.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; + +public class Grid extends Mesh { + + 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 000000000..5b6592d10 --- /dev/null +++ b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.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.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(); + } +} 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 000000000..2e49ce504 --- /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 000000000..926ddab11 --- /dev/null +++ b/engine/src/core/com/jme3/scene/debug/SkeletonWire.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.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.ByteBuffer; +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 000000000..50af28a92 --- /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 000000000..280475033 --- /dev/null +++ b/engine/src/core/com/jme3/scene/debug/WireFrustum.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.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.setUpdateNeeded(); + + 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 000000000..df863e2d9 --- /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 000000000..8bc7c21b7 --- /dev/null +++ b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.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.scene.mesh; + +import java.nio.Buffer; + +/** + * IndexBuffer 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 { + public abstract int get(int i); + public abstract void put(int i, int value); + public abstract int size(); + 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 000000000..649c6bdcd --- /dev/null +++ b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.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.ByteBuffer; + +/** + * + * @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 000000000..e062cfd0a --- /dev/null +++ b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.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.scene.mesh; + +import java.nio.Buffer; +import java.nio.IntBuffer; + +/** + * + * @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 000000000..8abccb752 --- /dev/null +++ b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.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.scene.mesh; + +import java.nio.Buffer; +import java.nio.ShortBuffer; + +/** + * + * @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 000000000..1fd4007bd --- /dev/null +++ b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java @@ -0,0 +1,88 @@ +package com.jme3.scene.mesh; + +import com.jme3.scene.Mesh.Mode; +import java.nio.Buffer; + +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; + } + } + + @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 000000000..464d988fa --- /dev/null +++ b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java @@ -0,0 +1,76 @@ +package com.jme3.scene.mesh; + +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.Buffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +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.getIndexBuffer(); + if (inBuf == null){ + inBuf = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode()); + }else{ + inBuf = new WrappedIndexBuffer(mesh); + } + + IndexBuffer outBuf; + if (inBuf.size() > Short.MAX_VALUE * 2){ + outBuf = new IndexIntBuffer(BufferUtils.createIntBuffer(inBuf.size())); + }else{ + outBuf = new IndexShortBuffer(BufferUtils.createShortBuffer(inBuf.size())); + } + + for (int i = 0; i < inBuf.size(); i++){ + outBuf.put(i, inBuf.get(i)); + } + + mesh.clearBuffer(Type.Index); + mesh.setMode(Mode.Triangles); + 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/shape/AbstractBox.java b/engine/src/core/com/jme3/scene/shape/AbstractBox.java new file mode 100644 index 000000000..bd1e4baf7 --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/AbstractBox.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import java.io.IOException; + +/** + * An eight sided box. + *

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

+ * This class does not control how the geometry data is generated, see {@link Box} + * and {@link StripBox} for that. + * + * @author Ian Phillips + * @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 tri-mesh. + */ + 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. + *

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

+ * These eight points are determined from the minimum and maximum point. + */ + protected abstract void duUpdateGeometryVertices(); + + /** Get the centre 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. + *

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

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

+ * 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); + } + + 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())); + } + + 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 000000000..4713d9d1b --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Box.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. + */ + +// $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 + }; + + private static final long serialVersionUID = 1L; + + /** + * Creates a new box. + *

+ * The box has a center of 0,0,0 and extends in the out from the center by + * the given amount in each direction. So, for example, a box + * with extent of 0.5 would be the unit cube. + * + * @param name the name 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(float x, float y, float z) { + super(); + updateGeometry(Vector3f.ZERO, x, y, z); + } + + /** + * Creates a new box. + *

+ * The box has the given center and extends in the out from the center by + * the given amount in each direction. So, for example, a box + * with extent of 0.5 would be the unit cube. + * + * @param name the name of the box. + * @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 Box object. + *

+ * 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 name the name of the box. + * @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. + *

+ * 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 000000000..d7d0a4745 --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Curve.java @@ -0,0 +1,184 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +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; + +/** + * + * @author Nehon + */ +public class Curve extends Mesh { + + private Spline spline; + private Vector3f temp = new Vector3f(); + + /** + * 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 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 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++; + } + + setMode(Mesh.Mode.Lines); + setBuffer(VertexBuffer.Type.Position, 3, array); + setBuffer(VertexBuffer.Type.Index, ((spline.getControlPoints().size() - 1) * nbSubSegments) * 2, indices); + updateBound(); + 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 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(); + } + + 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 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++; + } + } + + setMode(Mesh.Mode.Lines); + setBuffer(VertexBuffer.Type.Position, 3, array); + setBuffer(VertexBuffer.Type.Index, (spline.getControlPoints().size() - 1) * 2, indices); + updateBound(); + updateCounts(); + } +} 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 000000000..fa077d8c7 --- /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.IndexBuffer; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +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.
+ * 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.
+ * 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; + } + + /** + * Set the half angle of the cone. + * + * @param radians + */ + public void setHalfAngle(float radians) { + updateGeometry(getAxisSamples(), getRadialSamples(), FastMath.tan(radians), getRadius2(), getHeight(), isClosed(), isInverted()); + } + + /** + * Set the bottom radius of the 'cylinder' to differ from the top radius. + * This makes the Geometry be a frustum of pyramid, or if set to 0, a cone. + *

+ * Note: this method causes the tri-mesh geometry data + * to be recalculated, see + * the package description for more information about this. + * + * @param radius the second radius to set. + * @see {@link Cone} + * @deprecated use {@link #recomputeGeometry(int, int, float, float, boolean, boolean)}. + */ + public void setRadius2(float radius2) { + updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, 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]; + + 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; + } + } + float z = -halfHeight + height * axisFraction; + + // compute center of slice + 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); + if (topBottom == 0) { + if (!inverted) + nb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z); + else + nb.put(-tempNormal.x).put(-tempNormal.y).put(-tempNormal.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 000000000..0e9a3dafa --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Dome.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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.scene.*; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +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 outsideView = true; + + /** + * Constructs a dome. By default the dome has not geometry data or center. + */ + public Dome() { + } + + /** + * Constructs a dome with center at the origin. For details, see the other + * constructor. + * + * @param name + * Name of dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The samples along the radial. + * @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. All geometry data buffers are updated automatically. + * Both planes and radialSamples increase the quality of the generated dome. + * + * @param name + * Name of the dome. + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The number of samples along the radial. + * @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. All geometry data buffers are updated automatically. + * Both planes and radialSamples increase the quality of the generated dome. + * + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the dome. + * @param outsideView + * If true, the triangles will be connected for a view outside of + * the dome. + */ + public Dome(Vector3f center, int planes, int radialSamples, + float radius, boolean outsideView) { + super(); + updateGeometry(center, planes, radialSamples, radius, outsideView); + } + + 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 aview out from the dome or not. + * + * @return + */ + public boolean isOutsideView() { + return outsideView; + } + + /** + * 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 outsideView 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 outsideView) { + this.outsideView = outsideView; + 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(); + assert vars.lock(); + 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 (outsideView) + 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); + } + + assert vars.unlock(); + + // pole + vb.put(center.x).put(center.y + radius).put(center.z); + nb.put(0).put(outsideView ? 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) { + 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) { + ib.put((short)(bottomPlaneStart + samples)); + ib.put((short)(vertCount - 1)); + ib.put((short)(bottomPlaneStart + samples + 1)); + } + + updateBound(); + } + + 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()); + } + + /** + * Generates the connections + */ + 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 000000000..55f54bc21 --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Line.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 com.jme3.util.BufferUtils; +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 000000000..59854a052 --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/PQTorus.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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.IntBuffer; +import java.nio.ShortBuffer; + +/** + * A parameterized torus, also known as a pq 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 static final long serialVersionUID = 1L; + + private float p, q; + + private float radius, width; + + private int steps, radialSamples; + + public PQTorus() { + } + + /** + * Creates a parameterized torus. + *

+ * Steps and radialSamples are both degree of accuracy values. + * + * @param name the name of the torus. + * @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 000000000..a9464fc4c --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Quad.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.shape; + +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Type; + +public class Quad extends Mesh { + + private float width; + private float height; + + /** + * Do not use this constructor. Serialization purposes only. + */ + public Quad(){ + } + + public Quad(float width, float height){ + updateGeometry(width, height); + } + + 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 000000000..1e2034732 --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Sphere.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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.scene.*; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +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; + + +/** + * Sphere 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; + + /** + * Empty constructor for 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(); + assert vars.lock(); + 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++; + } + + assert vars.unlock(); + + // 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/Torus.java b/engine/src/core/com/jme3/scene/shape/Torus.java new file mode 100644 index 000000000..8cf43d34f --- /dev/null +++ b/engine/src/core/com/jme3/scene/shape/Torus.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. + */ + +// $Id: Torus.java 4131 2009-03-19 20:15:28Z blaine.dev $ +package com.jme3.scene.shape; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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. + *

+ * 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 name + * The name of the Torus. + * @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; + if (true) + fnb.put(tempNormal.x).put(tempNormal.y).put( + tempNormal.z); + else + 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 000000000..4b6a09555 --- /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 000000000..043897663 --- /dev/null +++ b/engine/src/core/com/jme3/shader/DefineList.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.shader; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +public class DefineList implements Savable { + + private final SortedMap defines = new TreeMap(); + 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 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){ + compiled = null; + return defines.get(key); + } + +// public void set(String key, String val){ +// compiled = null; +// defines.put(key, val); +// } + + public void set(String key, VarType type, Object val){ + compiled = null; + if (val == null){ + defines.remove(key); + return; + } + + switch (type){ + case Boolean: + if ( ((Boolean) val).booleanValue() ) + defines.put(key, "1"); + else if (defines.containsKey(key)) + defines.remove(key); + + break; + case Float: + case Int: + defines.put(key, val.toString()); + break; + default: + defines.put(key, "1"); + break; + } + } + + public void remove(String key){ + compiled = null; + defines.remove(key); + } + + public void addFrom(DefineList other){ + compiled = null; + if (other == null) + return; + + defines.putAll(other.defines); + } + + public String getCompiled(){ + if (compiled == null){ + StringBuilder sb = new StringBuilder(); + for (Map.Entry 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 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 000000000..2670e4c8f --- /dev/null +++ b/engine/src/core/com/jme3/shader/Shader.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.shader; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.renderer.GLObject; +import com.jme3.renderer.Renderer; +import com.jme3.util.ListMap; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +public final class Shader extends GLObject 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 shaderList; + + /** + * Maps uniform name to the uniform variable. + */ +// private HashMap uniforms; + private ListMap uniforms; + + /** + * Maps attribute name to the location of the attribute in the shader. + */ + private HashMap 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 class ShaderSource extends GLObject implements Savable { + + ShaderType shaderType; + + boolean usable = false; + String name = null; + String source = null; + String defines = null; + + public ShaderSource(ShaderType type){ + super(Type.ShaderSource); + this.shaderType = type; + if (type == null) + throw new NullPointerException("The shader type must be specified"); + } + + protected ShaderSource(ShaderSource ss){ + super(Type.ShaderSource, ss.id); + this.shaderType = ss.shaderType; + usable = false; + name = ss.name; + // forget source & defines + } + + public ShaderSource(){ + super(Type.ShaderSource); + } + + 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(Renderer r){ + r.deleteShaderSource(this); + } + + public GLObject createDestructableClone(){ + return new ShaderSource(this); + } + } + + /** + * Create an empty shader. + */ + public Shader(String language){ + super(Type.Shader); + this.language = language; + shaderList = new ArrayList(); +// uniforms = new HashMap(); + uniforms = new ListMap(); + attribs = new HashMap(); + } + + /** + * Do not use this constructor. Serialization purposes only. + */ + public Shader(){ + super(Type.Shader); + } + + protected Shader(Shader s){ + super(Type.Shader, s.id); + shaderList = new ArrayList(); +// uniforms = new HashMap(); + uniforms = new ListMap(); + attribs = new HashMap(); + for (ShaderSource source : s.shaderList){ + addSource((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.writeStringSavableMap(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 = (HashMap) ic.readStringSavableMap("attribs", null); + + HashMap uniMap = (HashMap) ic.readStringSavableMap("uniforms", null); + uniforms = new ListMap(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 + */ + public 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(String name){ + Attribute attrib = attribs.get(name); + if (attrib == null){ + attrib = new Attribute(); + attrib.name = name; + attribs.put(name, attrib); + } + return attrib; + } + +// public Collection getUniforms(){ +// return uniforms.values(); +// } + + public ListMap getUniformMap(){ + return uniforms; + } + + public Collection getAttributes() { + return attribs.values(); + } + + public Collection 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. + * @return + */ + 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 (Attribute attrib : attribs.values()){ + attrib.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. + * @param r + */ + @Override + public void resetObject() { + this.id = -1; + this.usable = false; + setUpdateNeeded(); + // Already done by the call above + //resetLocations(); + } + + @Override + public void deleteObject(Renderer r) { + r.deleteShader(this); + } + + public GLObject 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 000000000..a4c18ebfb --- /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 { + + 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 000000000..8aecdebdd --- /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 000000000..02f20dace --- /dev/null +++ b/engine/src/core/com/jme3/shader/ShaderVariable.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.shader; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 000000000..26cf941c0 --- /dev/null +++ b/engine/src/core/com/jme3/shader/Uniform.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +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="); + } + } + 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; + } + } + + 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 000000000..6a4f07615 --- /dev/null +++ b/engine/src/core/com/jme3/shader/UniformBinding.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.light.AmbientLight; + +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 View 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, + + /** + * AmbientLightColor. + * The sum of all the colors in the LightList with type + * {@link AmbientLight}. + * Type: vec4 + */ + @Deprecated + AmbientLightColor; +} 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 000000000..7252285d1 --- /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/shader/plugins/GLSLLoader.java b/engine/src/core/com/jme3/shader/plugins/GLSLLoader.java new file mode 100644 index 000000000..c7da6c6d0 --- /dev/null +++ b/engine/src/core/com/jme3/shader/plugins/GLSLLoader.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 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.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * GLSL File parser that supports #import pre-processor statement + */ +public class GLSLLoader implements AssetLoader { + + private AssetManager owner; + private Map dependCache = new HashMap(); + + private class DependencyNode { + + private String shaderSource; + private String shaderName; + + private final Set dependsOn = new HashSet(); + private final Set dependOnMe = new HashSet(); + + 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 { + 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 checkedNodes){ + Collection allNodes = dependCache.values(); + if (allNodes == null || allNodes.size() == 0) + return null; + + for (DependencyNode node : allNodes){ + if (node.dependsOn.size() == 0){ + 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 checkedNodes = new ArrayList(); + 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 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/com/jme3/system/Annotations.java b/engine/src/core/com/jme3/system/Annotations.java new file mode 100644 index 000000000..e68142d4e --- /dev/null +++ b/engine/src/core/com/jme3/system/Annotations.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.system; + +import checkers.quals.TypeQualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 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 000000000..8fcfb6c73 --- /dev/null +++ b/engine/src/core/com/jme3/system/AppSettings.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.image.BufferedImage; +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; + +public class AppSettings extends HashMap { + + private static final AppSettings defaults = new AppSettings(false); + public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1", + LWJGL_OPENGL2 = "LWJGL-OpenGL2", + LWJGL_OPENGL3 = "LWJGL-OpenGL3", + JOGL = "JOGL", + NULL = "NULL"; + public static final String LWJGL_OPENAL = "LWJGL"; + private String settingsDialogImage = "/com/jme3/app/Monkey.png"; + + 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("Icons", null); + + // disable these settings to benchmark speed +// defaults.put("VSync", true); +// defaults.put("FrameRate", 60); + } + + /** + * Create Application settings + * use loadDefault=true, to load jME default values. + * use false if you want to change some settings but you would like the application to remind other settings from previous launches + * @param loadDefaults + */ + public AppSettings(boolean loadDefaults) { + if (loadDefaults) { + putAll(defaults); + } + } + + public void copyFrom(AppSettings other) { + this.putAll(other); + } + + public void mergeFrom(AppSettings other) { + for (String key : other.keySet()) { + if (get(key) == null) { + put(key, other.get(key)); + } + } + } + + public void load(InputStream in) throws IOException { + Properties props = new Properties(); + props.load(in); + for (Map.Entry 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); + } + } + } + + public void save(OutputStream out) throws IOException { + Properties props = new Properties(); + for (Map.Entry 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"); + } + + 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)); + } + } + } + } + + public void save(String preferencesKey) throws BackingStoreException { + Preferences prefs = Preferences.userRoot().node(preferencesKey); + for (String key : keySet()) { + prefs.put(key, get(key).toString()); + } + } + + public int getInteger(String key) { + Integer i = (Integer) get(key); + if (i == null) { + return 0; + } + + return i.intValue(); + } + + public boolean getBoolean(String key) { + Boolean b = (Boolean) get(key); + if (b == null) { + return false; + } + + return b.booleanValue(); + } + + public String getString(String key) { + String s = (String) get(key); + if (s == null) { + return null; + } + + return s; + } + + public void putInteger(String key, int value) { + put(key, Integer.valueOf(value)); + } + + public void putBoolean(String key, boolean value) { + put(key, Boolean.valueOf(value)); + } + + public void putString(String key, String value) { + put(key, value); + } + + public void setFrameRate(int frameRate) { + putInteger("FrameRate", frameRate); + } + + public void setUseInput(boolean use) { + putBoolean("UseInput", use); + } + + public void setUseJoysticks(boolean use) { + putBoolean("DisableJoysticks", !use); + } + + public void setRenderer(String renderer) { + putString("Renderer", renderer); + } + + public void setCustomRenderer(Class clazz){ + put("Renderer", "CUSTOM" + clazz.getName()); + } + + public void setAudioRenderer(String audioRenderer) { + putString("AudioRenderer", audioRenderer); + } + + public void setWidth(int value) { + putInteger("Width", value); + } + + public void setHeight(int value) { + putInteger("Height", value); + } + + public void setResolution(int width, int height) { + setWidth(width); + setHeight(height); + } + + public void setFrequency(int value) { + putInteger("Frequency", value); + } + + public void setBitsPerPixel(int value) { + putInteger("BitsPerPixel", value); + } + + public void setSamples(int value) { + putInteger("Samples", value); + } + + public void setTitle(String title) { + putString("Title", title); + } + + public void setFullscreen(boolean value) { + putBoolean("Fullscreen", value); + } + + public void setVSync(boolean value) { + putBoolean("VSync", 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. + *
+ * 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. + */ + public void setIcons(BufferedImage[] value) { + put("Icons", value); + } + + public int getFrameRate() { + return getInteger("FrameRate"); + } + + public boolean useInput() { + return getBoolean("UseInput"); + } + + public String getRenderer() { + return getString("Renderer"); + } + + public int getWidth() { + return getInteger("Width"); + } + + public int getHeight() { + return getInteger("Height"); + } + + public int getBitsPerPixel() { + return getInteger("BitsPerPixel"); + } + + public int getFrequency() { + return getInteger("Frequency"); + } + + public int getDepthBits() { + return getInteger("DepthBits"); + } + + public int getStencilBits() { + return getInteger("StencilBits"); + } + + public int getSamples() { + return getInteger("Samples"); + } + + public String getTitle() { + return getString("Title"); + } + + public boolean isVSync() { + return getBoolean("VSync"); + } + + public boolean isFullscreen() { + return getBoolean("Fullscreen"); + } + + public boolean useJoysticks() { + return !getBoolean("DisableJoysticks"); + } + + public String getAudioRenderer() { + return getString("AudioRenderer"); + } + + public BufferedImage[] getIcons() { + return (BufferedImage[]) get("Icons"); + } + + public void setSettingsDialogImage(String path) { + settingsDialogImage = path; + } + + public String getSettingsDialogImage() { + return 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 000000000..0c1d35bf7 --- /dev/null +++ b/engine/src/core/com/jme3/system/JmeContext.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.system; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +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 onscreen surface + * enclosed in a system defined by the operating system. Implementations + * are encourged 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, + + /** + * + */ + Canvas, + + /** + * An OffscreenSurface 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 Headless 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 restart() 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 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(); + + /** + * @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. + */ + @Deprecated + public void create(); + + /** + * 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. + */ + @Deprecated + public void destroy(); + + /** + * 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/NanoTimer.java b/engine/src/core/com/jme3/system/NanoTimer.java new file mode 100644 index 000000000..13a150a51 --- /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; + +/** + * NanoTimer is a System.nanoTime implementation of Timer. + * 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 000000000..ed53b7251 --- /dev/null +++ b/engine/src/core/com/jme3/system/NullContext.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.system; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +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 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; + } + +} 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 000000000..68672f858 --- /dev/null +++ b/engine/src/core/com/jme3/system/NullRenderer.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.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 = EnumSet.noneOf(Caps.class); + private static final Statistics stats = new Statistics(); + + public EnumSet getCaps() { + return caps; + } + + public Statistics getStatistics() { + return stats; + } + + 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 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/SystemListener.java b/engine/src/core/com/jme3/system/SystemListener.java new file mode 100644 index 000000000..e1ccf579f --- /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 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 000000000..0974c7b87 --- /dev/null +++ b/engine/src/core/com/jme3/system/Timer.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.system; + +/** + * Timer 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 getResolution(). 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(); + + /** + * update 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/terrain/AbstractGeomap.java b/engine/src/core/com/jme3/terrain/AbstractGeomap.java new file mode 100644 index 000000000..7caa59543 --- /dev/null +++ b/engine/src/core/com/jme3/terrain/AbstractGeomap.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.math.Vector2f; +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.BufferUnderflowException; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +/** + * implements all writeXXXXArray methods to reduce boilerplate code + * Geomap implementations are encourged to extend this class + */ +public abstract class AbstractGeomap implements Geomap { + + 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; + } + + /* + * (non-Javadoc) + * Subclasses are encourged to provide a better implementation + * which directly accesses the data rather than using getHeight + */ + public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center){ + // check now so we don't have an NPE at the loop (getHeight call) + if (!isLoaded()) + throw new NullPointerException(); + + if (store!=null){ + // you might want to write this data as a part of a larger + // vertex array/buffer to reduce draw calls and that is + // why remaining() is used instead of limit() + if (store.remaining() < getWidth()*getHeight()*3) + throw new BufferUnderflowException(); + }else{ + // allocate a new buffer, this buffer is accessible to the user + // through the return of this method.. + store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*3); + } + + Vector3f offset = new Vector3f(-getWidth() * scale.x, 0, -getWidth() * scale.z); + if (!center) + offset.zero(); + + // going through Y/Z first, scanlines are horizontal + for (int z = 0; z < getHeight(); z++){ + for (int x = 0; x < getWidth(); x++){ + store.put( (float)x*scale.x + offset.x ); + store.put( (float)getValue(x,z)*scale.y ); + store.put( (float)z*scale.z + offset.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; + } + + /** + * This one has indexed LOD + * @param store + * @return + */ + public IntBuffer writeIndexArray2(IntBuffer store){ + int faceN = (getWidth()-1)*(getHeight()-1)*2; + + final int lod2 = 32; + final int lod = 1; + + if (store!=null){ + if (store.remaining() < faceN*3) + throw new BufferUnderflowException(); + }else{ + store = BufferUtils.createIntBuffer(faceN*3); + } + + for (int z = 0; z < getHeight()-1; z += lod ){ + for (int x = 0; x < getWidth()-1; x += lod){ + int i = x + z * getWidth(); + store.put(i).put(i+getWidth()*lod).put(i+getWidth()*lod+lod); + store.put(i+getWidth()*lod+lod).put(i+lod).put(i); + } + } + store.flip(); + + for(int i = 0; i < store.limit(); i++ ){ + int index = store.get(); + if( index < getWidth()-3 && index % lod2 != 0 ){ + store.position(store.position()-1); + store.put(index - index % lod2); + } + } + + return store; + } + + /* + * (non-Javadoc) + * Subclasses are encourged to provide a better implementation + * which directly accesses the data rather than using getNormal + */ + 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(); + + if (!hasNormalmap()){ + 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++; + } + } + }else{ + Vector3f temp = new Vector3f(); + for (int z = 0; z < getHeight(); z++){ + for (int x = 0; x < getWidth(); x++){ + getNormal(x,z,temp); + store.put(temp.x).put(temp.y).put(temp.z); + } + } + } + + return store; + } + + + /* + * (non-Javadoc) + * ... + */ + 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 ); + +// store.put( ((float)x) / getWidth() ); +// store.put( ((float)y) / getHeight() ); + } + + } + + 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; + } + +} diff --git a/engine/src/core/com/jme3/terrain/BufferGeomap.java b/engine/src/core/com/jme3/terrain/BufferGeomap.java new file mode 100644 index 000000000..b76d4b02d --- /dev/null +++ b/engine/src/core/com/jme3/terrain/BufferGeomap.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +/** + * Implementation of the Geomap interface which stores data in memory as native buffers + */ +public class BufferGeomap extends AbstractGeomap implements Geomap, Savable { + + protected FloatBuffer hdata; + protected ByteBuffer ndata; + protected int width, height, maxval; + + public BufferGeomap() {} + + public BufferGeomap(FloatBuffer heightData, ByteBuffer normalData, int width, int height, int maxval){ + this.hdata = heightData; + this.ndata = normalData; + this.width = width; + this.height = height; + this.maxval = maxval; + } + + public BufferGeomap(int width, int height, int maxval) { + this(ByteBuffer.allocateDirect(width*height*4).asFloatBuffer(),null,width,height,maxval); + } + + public FloatBuffer getHeightData(){ + if (!isLoaded()) + return null; + + return hdata; + } + + public ByteBuffer getNormalData(){ + if (!isLoaded() || !hasNormalmap()) + return null; + + return ndata; + } + + public int getMaximumValue(){ + return maxval; + } + + public float getValue(int x, int y) { + return hdata.get(y*width+x); + } + + public float getValue(int i) { + return hdata.get(i); + } + + public Vector3f getNormal(int x, int y, Vector3f store) { + return getNormal(y*width+x,store); + } + + public Vector3f getNormal(int i, Vector3f store) { + ndata.position( i*3 ); + if (store==null) store = new Vector3f(); + store.setX( (((float)(ndata.get() & 0xFF)/255f)-0.5f)*2f ); + store.setY( (((float)(ndata.get() & 0xFF)/255f)-0.5f)*2f ); + store.setZ( (((float)(ndata.get() & 0xFF)/255f)-0.5f)*2f ); + return store; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public SharedGeomap getSubGeomap(int x, int y, int w, int h) { + if (w+x > width) + w = width - x; + if (h+y > height) + h = height - y; + + return new SharedBufferGeomap(this,x,y,w,h); + } + + public Geomap copySubGeomap(int x, int y, int w, int h){ + FloatBuffer nhdata = ByteBuffer.allocateDirect(w*h*4).asFloatBuffer(); + hdata.position(y*width+x); + for (int cy = 0; cy < height; cy++){ + hdata.limit(hdata.position()+w); + nhdata.put(hdata); + hdata.limit(hdata.capacity()); + hdata.position(hdata.position()+width); + } + nhdata.flip(); + + ByteBuffer nndata = null; + if (ndata!=null){ + nndata = ByteBuffer.allocateDirect(w*h*3); + ndata.position( (y*width+x)*3 ); + for (int cy = 0; cy < height; cy++){ + ndata.limit(ndata.position()+w*3); + nndata.put(ndata); + ndata.limit(ndata.capacity()); + ndata.position(ndata.position()+width*3); + } + nndata.flip(); + } + + return new BufferGeomap(nhdata,nndata,w,h,maxval); + } + + public boolean hasNormalmap() { + return ndata != null; + } + + public boolean isLoaded() { + return true; + } + +// @Override +// public FloatBuffer writeNormalArray(FloatBuffer store) { +// if (!isLoaded() || !hasNormalmap()) throw new NullPointerException(); +// +// if (store!=null){ +// if (store.remaining() < width*height*3) +// throw new BufferUnderflowException(); +// }else{ +// store = BufferUtils.createFloatBuffer(width*height*3); +// } +// ndata.rewind(); +// +// for (int z = 0; z < height; z++){ +// for (int x = 0; x < width; x++){ +// float r = ((float)(ndata.get() & 0xFF)/255f -0.5f) * 2f; +// float g = ((float)(ndata.get() & 0xFF)/255f -0.5f) * 2f; +// float b = ((float)(ndata.get() & 0xFF)/255f -0.5f) * 2f; +// store.put(r).put(b).put(g); +// } +// } +// +// return store; +// } + + @Override + public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) { + if (!isLoaded()) throw new NullPointerException(); + + if (store!=null){ + if (store.remaining() < width*height*3) + throw new BufferUnderflowException(); + }else{ + store = BufferUtils.createFloatBuffer(width*height*3); + } + hdata.rewind(); + + assert hdata.limit() == height*width; + + Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f, + 0, + -getWidth() * scale.z * 0.5f); + if (!center) + offset.zero(); + + 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.get()*scale.y ); + store.put( (float)z*scale.z + offset.z ); + } + } + + return store; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(hdata, "hdata", 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.readFloatBuffer("hdata", null); + width = ic.readInt("width", 0); + height = ic.readInt("height", 0); + maxval = ic.readInt("maxval", 0); + } + + /** + * Populate the height data from the supplied mesh. + * The mesh's dimensions should be the same as width and height + * of this geomap + */ + public void populateHdataFromMesh(Mesh mesh) { + hdata = BufferUtils.createFloatBuffer(width*height); + hdata.rewind(); + VertexBuffer pb = mesh.getBuffer(Type.Position); + FloatBuffer fb = (FloatBuffer) pb.getData(); + for (int r=0; rgetValue() can + * return. Mostly depends on the source data format (byte, short, int, etc). + */ + public int getMaximumValue(); + + /** + * 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 arbitary height looked up from the heightmap + * + * @throws NullPointerException If isLoaded() is false + */ + public float getValue(int x, int y); + + /** + * 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 arbitary height looked up from the heightmap + * + * @throws NullPointerException If isLoaded() is false + */ + public float getValue(int i); + + /** + * Returns the normal at a point + * + * If store is null, then a new vector is returned, + * otherwise, the result is stored in the provided vector + * and then returned from this method + * + * @param x the X coordinate + * @param y the Y coordinate + * @param store A preallocated vector for storing the normal data, optional + * @returns store, or a new vector with the normal data if store is null + * + * @throws NullPointerException If isLoaded() or hasNormalmap() is false + */ + public Vector3f getNormal(int x, int y, Vector3f store); + + /** + * Returns the normal at an index + * + * If store is null, then a new vector is returned, + * otherwise, the result is stored in the provided vector + * and then returned from this method + * + * See getHeight(int) for information about index lookup + * + * @param i the index + * @param store A preallocated vector for storing the normal data, optional + * @returns store, or a new vector with the normal data if store is null + * + * @throws NullPointerException If isLoaded() or hasNormalmap() is false + */ + public Vector3f getNormal(int i, Vector3f store); + + public Vector2f getUV(int x, int y, Vector2f store); + public Vector2f getUV(int i, Vector2f store); + + /** + * Returns the width of this Geomap + * + * @returns the width of this Geomap + */ + public int getWidth(); + + /** + * Returns the height of this Geomap + * + * @returns the height of this Geomap + */ + public int getHeight(); + + /** + * Returns a section of this geomap as a new geomap + * + * The created geomap references data from this geomap + * If part of the geomap is contained within the original, + * then that part is returned + */ + public SharedGeomap getSubGeomap(int x, int y, int w, int h); + + /** + * Copies a section of this geomap as a new geomap + */ + public Geomap copySubGeomap(int x, int y, int w, int h); + + /** + * 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); + + /** + * 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); + + public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale); + + public IntBuffer writeIndexArray(IntBuffer store); + + public Mesh createMesh(Vector3f scale, Vector2f tcScale, boolean center); +} diff --git a/engine/src/core/com/jme3/terrain/GeomapLoader.java b/engine/src/core/com/jme3/terrain/GeomapLoader.java new file mode 100644 index 000000000..1b5f09f9d --- /dev/null +++ b/engine/src/core/com/jme3/terrain/GeomapLoader.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.terrain; +// +//import java.awt.Graphics2D; +//import java.awt.image.BufferedImage; +//import java.io.IOException; +//import java.io.InputStream; +//import java.net.URL; +//import java.nio.ByteBuffer; +//import java.nio.ByteOrder; +//import java.nio.IntBuffer; +//import javax.imageio.ImageIO; +// +//public class GeomapLoader { +// +// /** +// * Loads a heightmap from the image +// * +// * The returned IntBuffer has heights ranging from 0 to 2^16 +// */ +// protected static IntBuffer loadHeightmapImage(BufferedImage bi, boolean invertY){ +// BufferedImage hm = null; +// +// if (bi.getType()!=BufferedImage.TYPE_USHORT_GRAY || invertY){ +// hm = new BufferedImage(bi.getWidth(),bi.getHeight(),BufferedImage.TYPE_USHORT_GRAY); +// +// Graphics2D g = hm.createGraphics(); +// if (invertY){ +// g.scale(1d,-1d); +// g.translate(0, -bi.getHeight()); +// } +// g.drawImage(bi,null,0,0); +// g.dispose(); +// }else{ +// hm = bi; +// } +// +// IntBuffer ib = BufferUtils.createIntBuffer(bi.getWidth()*bi.getHeight()); +// short[] data = (short[]) hm.getData().getDataElements(0,0,bi.getWidth(),bi.getHeight(),null); +// for (int i = 0; i < hm.getWidth()*hm.getHeight(); i++){ +// //System.out.println(data[i] & 0xFFFF); +//// ib.put( (((data[i] & 0xFF00) >> 8) | ((data[i] & 0x00FF) << 8)) ); +// ib.put( data[i] & 0xFFFF ); +// } +// +// //for (int y = 0; y < hm.getHeight(); y++){ +// // for (int x = 0; x < hm.getWidth(); x++){ +// // ib.put(db.getElem +// // } +// //} +// ib.flip(); +// +// return ib; +// } +// +// protected static IntBuffer loadHeightmapRAW(ByteBuffer raw, int width, int height, ByteOrder order, int bitsize){ +// raw.order(order); +// +// if (bitsize == 32){ +// // directly map to heightmap +// raw = raw.duplicate(); +// raw.limit(width * height * 4); +// return raw.asIntBuffer(); +// } +// +// ByteBuffer target = ByteBuffer.allocateDirect(width * height * 4); +// switch (bitsize){ +// case 8: +// for (int i = 0, len = width*height; i < len; i++){ +// target.putInt(raw.get() & 0xFF); +// } +// break; +// case 16: +// for (int i = 0, len = width*height; i < len; i++){ +// target.putInt(raw.getShort() & 0xFFFF); +// } +// break; +// case 24: +// //for (int i = 0, len = width*height; i < len; i++){ +// //} +// throw new UnsupportedOperationException("24 bitsize is not supported!"); +// case 32: +// //for (int i = 0, len = width*height; i < len; i++){ +// // target.putInt(raw.getInt() & 0xFFFFFFFF); +// //} +// target.put(raw); +// break; +// } +// target.flip(); +// +// return target.asIntBuffer(); +// } +// +// public static Geomap fromRaw(URL rawURL, int width, int height, int bitsize) throws IOException{ +// InputStream in = rawURL.openStream(); +// +// // read bytes from stream +// byte[] data = new byte[width * height * bitsize / 8]; +// in.read(data); +// +// // convert to bytebuffer +// ByteBuffer bb = ByteBuffer.allocateDirect(data.length).order(ByteOrder.nativeOrder()); +// bb.put(data); +// bb.flip(); +// +// IntBuffer ib = loadHeightmapRAW(bb, width, height, ByteOrder.BIG_ENDIAN, bitsize); +// return new BufferGeomap(ib, null, width, height, (int)Math.pow(2, bitsize)); +// } +// +// public static Geomap fromImage(URL imageURL) throws IOException { +// BufferedImage image = ImageIO.read(imageURL); +// IntBuffer ib = loadHeightmapImage(image, false); +// // maximum value is of unsigned short, because loadHeightmapImage +// // converts the image into a TYPE_USHORT_GRAY before reading in the +// // values. +// return new BufferGeomap(ib, null, image.getWidth(), image.getHeight(), 65536); +// } +// +//} diff --git a/engine/src/core/com/jme3/terrain/SharedBufferGeomap.java b/engine/src/core/com/jme3/terrain/SharedBufferGeomap.java new file mode 100644 index 000000000..35369e978 --- /dev/null +++ b/engine/src/core/com/jme3/terrain/SharedBufferGeomap.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.terrain; + +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class SharedBufferGeomap extends AbstractGeomap implements SharedGeomap { + + protected final BufferGeomap parent; + protected final FloatBuffer hdata; + protected final ByteBuffer ndata; + protected final int startX, startY, width, height; + + public SharedBufferGeomap(BufferGeomap parent, int x, int y, int w, int h){ + this.parent = parent; + hdata = parent.getHeightData(); + ndata = parent.getNormalData(); + startX = x; + startY = y; + width = w; + height = h; + } + + public boolean hasNormalmap() { + return parent.hasNormalmap(); + } + + public boolean isLoaded() { + return parent.isLoaded(); + } + + public int getMaximumValue(){ + return parent.getMaximumValue(); + } + + public Geomap getParent() { + return parent; + } + + public int getXOffset() { + return startX; + } + + public int getYOffset() { + return startY; + } + + public float getValue(int x, int y) { + return parent.getValue(startX+x,startY+y); + } + + public float getValue(int i) { + int r = i % width; + return getValue(r,(i-r)/width); + } + + public Vector3f getNormal(int x, int y, Vector3f store) { + return parent.getNormal(startX+x,startY+y,store); + } + + public Vector3f getNormal(int i, Vector3f store) { + int r = i % width; + return getNormal(r,(i-r)/width,store); + } + + @Override + public Vector2f getUV(int x, int y, Vector2f store){ + return parent.getUV(startX+x, startY+y, store); + } + + @Override + public Vector2f getUV(int i, Vector2f store){ + int r = i % width; + return getUV(r,(i-r)/width,store); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public Geomap copy() { + return parent.copySubGeomap(startX,startY,width,height); + } + + public SharedGeomap getSubGeomap(int x, int y, int w, int h) { + if (x<0 || y<0 || x>width || y>height || w+x>width || h+y>height) + throw new IndexOutOfBoundsException(); + + return parent.getSubGeomap(startX+x,startY+y,w,h); + } + + public Geomap copySubGeomap(int x, int y, int w, int h) { + if (x<0 || y<0 || x>width || y>height || w>width || h>height) + throw new IndexOutOfBoundsException(); + + return parent.copySubGeomap(startX+x,startY+y,w,h); + } + +} diff --git a/engine/src/core/com/jme3/terrain/SharedGeomap.java b/engine/src/core/com/jme3/terrain/SharedGeomap.java new file mode 100644 index 000000000..d576ed44e --- /dev/null +++ b/engine/src/core/com/jme3/terrain/SharedGeomap.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 com.jme3.terrain; + +public interface SharedGeomap extends Geomap { + + /** + * Copies the data shared by this SharedGeomap into a new Geomap + */ + public Geomap copy(); + + /** + * Returns the Geomap from which the data is shared + */ + public Geomap getParent(); + + /** + * Returns the X offset of the shared Geomap relative to the parent origin + */ + public int getXOffset(); + + /** + * Returns the Y offset of the shared Geomap relative to the parent origin + */ + public int getYOffset(); + +} 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 000000000..e8ae1a74c --- /dev/null +++ b/engine/src/core/com/jme3/texture/FrameBuffer.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.GLObject; +import com.jme3.renderer.Renderer; +import com.jme3.texture.Image.Format; +import java.util.ArrayList; + +public class FrameBuffer extends GLObject { + + private int width = 0; + private int height = 0; + private int samples = 1; + private ArrayList colorBufs = new ArrayList(); + private RenderBuffer depthBuf = null; + private int colorBufIndex = 0; + + public class RenderBuffer { + + Texture tex; + Image.Format format; + int id = -1; + int slot = -1; + + public Format getFormat() { + return format; + } + + public Texture getTexture(){ + return tex; + } + + public int getId() { + return id; + } + + public void setId(int id){ + this.id = id; + } + + public int getSlot() { + return slot; + } + + public void setSlot(int slot) { + this.slot = 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 + "]"; + } + } + } + + public FrameBuffer(int width, int height, int samples){ + super(Type.FrameBuffer); + 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(Type.FrameBuffer, src.id); + /* + for (RenderBuffer renderBuf : src.colorBufs){ + RenderBuffer clone = renderBuf.createDestructableClone(); + if (clone != null) + this.colorBufs.add(clone); + } + + this.depthBuf = src.depthBuf.createDestructableClone(); + */ + } + + 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; + } + + 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"); + } + + public void setMultiTarget(boolean enabled){ + if (enabled) colorBufIndex = -1; + else colorBufIndex = 0; + } + + public void setTargetIndex(int index){ + if (index < 0 || index >= 16) + throw new IllegalArgumentException(); + + if (colorBufs.size() >= index) + throw new IndexOutOfBoundsException("The target at " + index + " is not set!"); + + colorBufIndex = index; + } + + public boolean isMultiTarget(){ + return colorBufIndex == -1; + } + + public int getTargetIndex(){ + return colorBufIndex; + } + + public void setColorTexture(Texture2D tex){ + clearColorTargets(); + addColorTexture(tex); + } + + public void clearColorTargets(){ + colorBufs.clear(); + } + + 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); + } + + 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(); + } + + public int getNumColorBuffers(){ + return colorBufs.size(); + } + + public RenderBuffer getColorBuffer(int index){ + return colorBufs.get(index); + } + + public RenderBuffer getColorBuffer() { + if (colorBufs.size() == 0) + return null; + + return colorBufs.get(0); + } + + public RenderBuffer getDepthBuffer() { + return depthBuf; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + 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(Renderer r) { + r.deleteFrameBuffer(this); + } + + public GLObject 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 000000000..a37477df0 --- /dev/null +++ b/engine/src/core/com/jme3/texture/Image.java @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.Renderer; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import com.jme3.export.Savable; +import com.jme3.renderer.GLObject; + +/** + * Image 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 GLObject implements Savable /*, Cloneable*/ { + + public enum Format { + Alpha8(8), + Alpha16(16), + + Luminance8(8), + Luminance16(16), + Luminance16F(16,true), + Luminance32F(32,true), + + Luminance8Alpha8(16), + Luminance16Alpha16(32), + Luminance16FAlpha16F(32,true), + + Intensity8(8), + Intensity16(16), + + BGR8(24), // BGR and ABGR formats are often used on windows systems + RGB8(24), + RGB10(30), + RGB16(48), + + RGB565(16), + ARGB4444(16), + RGB5A1(16), + RGBA8(32), + ABGR8(32), + RGBA16(64), + + DXT1(4,false,true, false), + DXT1A(4,false,true, false), + DXT3(8,false,true, false), + DXT5(8,false,true, false), + LATC(8, false, true, false), + + Depth(0,true,false,false), + Depth16(16,true,false,false), + Depth24(24,true,false,false), + Depth32(32,true,false,false), + Depth32F(32,true,false,true), + + RGB16F_to_RGB111110F(48,true), + RGB111110F(32,true), + RGB16F_to_RGB9E5(48,true), + RGB9E5(32,true), + + RGB16F(48,true), + RGBA16F(64,true), + RGB32F(96,true), + RGBA32F(128,true), + + 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; + } + + public int getBitsPerPixel(){ + return bpp; + } + + public boolean isDepthFormat(){ + return isDepth; + } + + public boolean isCompressed() { + return isCompressed; + } + + public boolean isFloatingPont(){ + return isFloatingPoint; + } + + } + + // image attributes + protected Format format; + protected int width, height, depth; + protected int[] mipMapSizes; + protected ArrayList data; + protected transient Object efficentData; + protected int multiSamples = 1; +// protected int mipOffset = 0; + + @Override + public void resetObject() { + this.id = -1; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Renderer r) { + r.deleteImage(this); + } + + @Override + public GLObject createDestructableClone() { + return new Image(id); + } + + public Image clone(){ + Image clone = (Image) super.clone(); + clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null; + clone.data = data != null ? new ArrayList(data) : null; + clone.setUpdateNeeded(); + return clone; + } + + /** + * Constructor instantiates a new Image object. All values + * are undefined. + */ + public Image() { + super(Type.Texture); + data = new ArrayList(1); + } + + protected Image(int id){ + super(GLObject.Type.Texture, id); + } + + /** + * Constructor instantiates a new Image 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 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 Image 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(1); + this.data.add(data); + } + this.mipMapSizes = mipMapSizes; + } + + /** + * Constructor instantiates a new Image object. The + * attributes of the image are defined during construction. + * + * @param type + * 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 data) { + this(format, width, height, depth, data, null); + } + + /** + * Constructor instantiates a new Image object. The + * attributes of the image are defined during construction. + * + * @param type + * 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; + } + + /** + * setData sets the data that makes up the image. This data + * is packed into an array of ByteBuffer objects. + * + * @param data + * the data that contains the image information. + */ + public void setData(ArrayList data) { + this.data = data; + setUpdateNeeded(); + } + + /** + * setData sets the data that makes up the image. This data + * is packed into a single ByteBuffer. + * + * @param data + * the data that contains the image information. + */ + public void setData(ByteBuffer data) { + this.data = new ArrayList(1); + this.data.add(data); + setUpdateNeeded(); + } + + public void addData(ByteBuffer data) { + if (this.data == null) + this.data = new ArrayList(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 efficent data representation of this image. + *

+ * Some system implementations are more efficent at operating + * on data other than ByteBuffers, in that case, this method can be used. + * + * @param efficentData + */ + public void setEfficentData(Object efficentData){ + this.efficentData = efficentData; + setUpdateNeeded(); + } + + /** + * @return The efficent data representation of this image. + * @see Image#setEfficentData(java.lang.Object) + */ + public Object getEfficentData(){ + return efficentData; + } + + /** + * 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(); + } + + /** + * setHeight 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(); + } + + /** + * setDepth 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(); + } + + /** + * setWidth 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(); + } + + /** + * setFormat 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(); + } + + /** + * getFormat returns the image format for this image. + * + * @return the image format. + * @see Format + */ + public Format getFormat() { + return format; + } + + /** + * getWidth returns the width of this image. + * + * @return the width of this image. + */ + public int getWidth() { + return width; + } + + /** + * getHeight returns the height of this image. + * + * @return the height of this image. + */ + public int getHeight() { + return height; + } + + /** + * getDepth returns the depth of this image (for 3d images). + * + * @return the depth of this image. + */ + public int getDepth() { + return depth; + } + + /** + * getData returns the data for this image. If the data is + * undefined, null will be returned. + * + * @return the data for this image. + */ + public List getData() { + return data; + } + + /** + * getData 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; + } + + 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; + } + + 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) capsule.readByteBufferArrayList("data", null); + } + +} diff --git a/engine/src/core/com/jme3/texture/ImageFlipper.java b/engine/src/core/com/jme3/texture/ImageFlipper.java new file mode 100644 index 000000000..f5be53709 --- /dev/null +++ b/engine/src/core/com/jme3/texture/ImageFlipper.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.texture; + +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; + +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/com/jme3/texture/Texture.java b/engine/src/core/com/jme3/texture/Texture.java new file mode 100644 index 000000000..0181d9c13 --- /dev/null +++ b/engine/src/core/com/jme3/texture/Texture.java @@ -0,0 +1,649 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.TextureKey; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Texture 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 + * Image 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, enivoronment - 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: + * mirrorClamp(f) = min(1, max(1/(2*N), + * abs(f))) 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: + * mirrorClampToBorder(f) = min(1+1/(2*N), max(1/(2*N), abs(f))) + * 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: + * mirrorClampToEdge(f) = min(1-1/(2*N), max(1/(2*N), abs(f))) + * 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 Texture 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; + } + + /** + * setImage 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; + } + + /** + * + * @param key + * @deprecated Use {@link Texture#setKey(com.jme3.asset.AssetKey) } + */ + @Deprecated + public void setTextureKey(TextureKey key){ + this.key = key; + } + + /** + * + * @return + * @deprecated Use {@link Texture#getKey() } + */ + @Deprecated + public TextureKey getTextureKey(){ + return key; + } + + /** + * getImage 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; + } + + /** + * setWrap 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); + + /** + * setWrap 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); + + /** + * getWrap 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(){ + String imgTxt = null; + if (image != null){ + imgTxt = ", img="+image.getWidth() + +"x"+image.getHeight(); + if (image.getDepth() > 1) + imgTxt += "x"+image.getDepth(); + imgTxt += "-"+image.getFormat().name(); + if (image.hasMipmaps()) + imgTxt += "/mips"; + } + + return getClass().getSimpleName() + "[name="+name+imgTxt+"]"; + } + +// public boolean equals(Object other) { +// if (other == this) { +// return true; +// } +// if (!(other instanceof Texture)) { +// return false; +// } +//// super.equals(other); +// +// Texture that = (Texture) other; +// if (this.textureId != that.textureId) +// return false; +// if (this.textureId == 0) { +// if (this.getImage() != null +// && !this.getImage().equals(that.getImage())) +// return false; +// if (this.getImage() == null && that.getImage() != null) +// return false; +// if (this.getAnisotropicFilterPercent() != that +// .getAnisotropicFilterPercent()) +// return false; +// if (this.getApply() != that.getApply()) +// return false; +// if (this.getCombineFuncAlpha() != that.getCombineFuncAlpha()) +// return false; +// if (this.getCombineFuncRGB() != that.getCombineFuncRGB()) +// return false; +// if (this.getCombineOp0Alpha() != that.getCombineOp0Alpha()) +// return false; +// if (this.getCombineOp1RGB() != that.getCombineOp1RGB()) +// return false; +// if (this.getCombineOp2Alpha() != that.getCombineOp2Alpha()) +// return false; +// if (this.getCombineOp2RGB() != that.getCombineOp2RGB()) +// return false; +// if (this.getCombineScaleAlpha() != that.getCombineScaleAlpha()) +// return false; +// if (this.getCombineScaleRGB() != that.getCombineScaleRGB()) +// return false; +// if (this.getCombineSrc0Alpha() != that.getCombineSrc0Alpha()) +// return false; +// if (this.getCombineSrc0RGB() != that.getCombineSrc0RGB()) +// return false; +// if (this.getCombineSrc1Alpha() != that.getCombineSrc1Alpha()) +// return false; +// if (this.getCombineSrc1RGB() != that.getCombineSrc1RGB()) +// return false; +// if (this.getCombineSrc2Alpha() != that.getCombineSrc2Alpha()) +// return false; +// if (this.getCombineSrc2RGB() != that.getCombineSrc2RGB()) +// return false; +// if (this.getEnvironmentalMapMode() != that +// .getEnvironmentalMapMode()) +// return false; +// if (this.getMagnificationFilter() != that.getMagnificationFilter()) +// return false; +// if (this.getMinificationFilter() != that.getMinificationFilter()) +// return false; +// if (this.getBlendColor() != null +// && !this.getBlendColor().equals(that.getBlendColor())) +// return false; +// if (this.getBlendColor() == null && that.getBlendColor() != null) +// return false; +// } +// return true; +// } + +// public abstract Texture createSimpleClone(); + + + /** Retreive 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); + 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 (key != null) { + Texture loadedTex = e.getAssetManager().loadTexture(key); + if (loadedTex == null) { + Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Could not load texture: {0}", key.toString()); + } else { + image = loadedTex.getImage(); + } + } +// image = (Image) capsule.readSavable("image", null); +// if (image == null) { +// } + 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 000000000..827446f1c --- /dev/null +++ b/engine/src/core/com/jme3/texture/Texture2D.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.texture; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +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); + } + + /** + * setWrap 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; + } + } + + /** + * setWrap 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; + } + + /** + * getWrap 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; + } + 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 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/TextureCubeMap.java b/engine/src/core/com/jme3/texture/TextureCubeMap.java new file mode 100644 index 000000000..83b6be281 --- /dev/null +++ b/engine/src/core/com/jme3/texture/TextureCubeMap.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.texture; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.GLObject; +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:
+ *
+ * 0 => Positive X (+x)
+ * 1 => Negative X (-x)
+ * 2 => Positive Y (+y)
+ * 3 => Negative Y (-y)
+ * 4 => Positive Z (+z)
+ * 5 => Negative Z (-z)
+ * + * @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); + } + + /** + * setWrap 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; + } + } + + /** + * setWrap 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; + } + + /** + * getWrap 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 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 000000000..13dd821ab --- /dev/null +++ b/engine/src/core/com/jme3/ui/Picture.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.ui; + +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture2D; + +/** + * A Picture 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; + private float height; + + public Picture(String name, boolean flipY){ + super(name, new Quad(1, 1, flipY)); + setQueueBucket(Bucket.Gui); + setCullHint(CullHint.Never); + } + + public Picture(String name){ + this(name, false); + } + + public Picture(){ + } + + public void setWidth(float width){ + this.width = width; + setLocalScale(new Vector3f(width, height, 1f)); + } + + public void setHeight(float height){ + this.height = height; + setLocalScale(new Vector3f(width, height, 1f)); + } + + public void setPosition(float x, float y){ + float z = getLocalTranslation().getZ(); + setLocalTranslation(x, y, z); + } + + public void setImage(AssetManager manager, String imgName, boolean useAlpha){ + TextureKey key = new TextureKey(imgName, true); + Texture2D tex = (Texture2D) manager.loadTexture(key); + setTexture(manager, tex, useAlpha); + } + + public void setTexture(AssetManager manager, Texture2D tex, boolean useAlpha){ + if (getMaterial() == null){ + Material mat = new Material(manager, "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 000000000..ba187a8d2 --- /dev/null +++ b/engine/src/core/com/jme3/util/BufferUtils.java @@ -0,0 +1,1085 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; + + +/** + * BufferUtils 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 { + + //// -- TEMP DATA OBJECTS -- //// +// private static final Vector2f _tempVec2 = new Vector2f(); +// private static final Vector3f _tempVec3 = new Vector3f(); +// private static final ColorRGBA _tempColor = new ColorRGBA(); + + //// -- TRACKER HASH -- //// + private static final Map trackingHash = Collections.synchronizedMap(new WeakHashMap()); + private static final Object ref = new Object(); + private static final boolean trackDirectMemory = false; + + //// -- GENERIC CLONE -- //// + + 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(); + } + } + + + //// -- VECTOR3F METHODS -- //// + + /** + * 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 color + * the data 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) { + assert TempVars.get().lock(); + Vector3f tempVec3 = TempVars.get().vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.normalizeLocal(); + setInBuffer(tempVec3, buf, index); + assert TempVars.get().unlock(); + } + + /** + * 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) { + assert TempVars.get().lock(); + Vector3f tempVec3 = TempVars.get().vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.addLocal(toAdd); + setInBuffer(tempVec3, buf, index); + assert TempVars.get().unlock(); + } + + /** + * 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) { + assert TempVars.get().lock(); + Vector3f tempVec3 = TempVars.get().vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.multLocal(toMult); + setInBuffer(tempVec3, buf, index); + assert TempVars.get().unlock(); + } + + /** + * 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) { + assert TempVars.get().lock(); + Vector3f tempVec3 = TempVars.get().vect1; + populateFromBuffer(tempVec3, buf, index); + boolean eq = tempVec3.equals(check); + assert TempVars.get().unlock(); + 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) { + assert TempVars.get().lock(); + Vector2f tempVec2 = TempVars.get().vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.normalizeLocal(); + setInBuffer(tempVec2, buf, index); + assert TempVars.get().unlock(); + } + + /** + * 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) { + assert TempVars.get().lock(); + Vector2f tempVec2 = TempVars.get().vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.addLocal(toAdd); + setInBuffer(tempVec2, buf, index); + assert TempVars.get().unlock(); + } + + /** + * 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) { + assert TempVars.get().lock(); + Vector2f tempVec2 = TempVars.get().vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.multLocal(toMult); + setInBuffer(tempVec2, buf, index); + assert TempVars.get().unlock(); + } + + /** + * 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) { + assert TempVars.get().lock(); + Vector2f tempVec2 = TempVars.get().vect2d; + populateFromBuffer(tempVec2, buf, index); + boolean eq = tempVec2.equals(check); + assert TempVars.get().unlock(); + 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(); + if (trackDirectMemory) { + trackingHash.put(buf, ref); + } + 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(); + if (trackDirectMemory) { + trackingHash.put(buf, ref); + } + 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(); + if (trackDirectMemory) { + trackingHash.put(buf, ref); + } + 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(); + if (trackDirectMemory) { + trackingHash.put(buf, ref); + } + 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(); + if (trackDirectMemory) { + trackingHash.put(buf, ref); + } + 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 required 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 required 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 bufs = new ArrayList(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()); + } + } + +} 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 000000000..cce44d31d --- /dev/null +++ b/engine/src/core/com/jme3/util/IntMap.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +/** + * Taken from http://code.google.com/p/skorpios/ + * + * @author Nate + */ +public final class IntMap implements Iterable, Cloneable { + + 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; + } + + public IntMap clone(){ + try{ + IntMap clone = (IntMap) 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 iterator() { + return (Iterator) new IntMapIterator(); + } + + final class IntMapIterator implements Iterator { + + /** + * Current entry. + */ + private Entry cur; + + /** + * Entry in the table + */ + private int idx = 0; + + /** + * Element in the entry + */ + private int el = 0; + + public IntMapIterator() { + cur = table[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 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; + } + + public String toString(){ + return key + " => " + value; + } + + public Entry clone(){ + try{ + Entry clone = (Entry) 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 000000000..998438a4f --- /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:
+ * 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 000000000..eefec07bc --- /dev/null +++ b/engine/src/core/com/jme3/util/ListMap.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.ArrayList; +import java.util.Collection; +import java.util.HashSet; +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 implements Map, Cloneable, Serializable { + + public static void main(String[] args){ + Map map = new ListMap(); + 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 implements Map.Entry, 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 clone(){ + return new ListMapEntry(key, value); + } + + } + + private final ArrayList> entries; + + public ListMap(){ + entries = new ArrayList>(); + } + + public ListMap(int initialCapacity){ + entries = new ArrayList>(initialCapacity); + } + + public ListMap(Map map){ + entries = new ArrayList>(map.size()); + putAll(map); + } + + public int size() { + return entries.size(); + } + + public Entry getEntry(int index){ + return entries.get(index); + } + + public V getValue(int index){ + return entries.get(index).value; + } + + public K getKey(int index){ + return entries.get(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) { + if (key == null) + throw new IllegalArgumentException(); + + for (int i = 0; i < entries.size(); i++){ + ListMapEntry entry = entries.get(i); + if (keyEq(entry.key, key)) + return true; + } + return false; + } + + public boolean containsValue(Object 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) { + if (key == null) + throw new IllegalArgumentException(); + + for (int i = 0; i < entries.size(); i++){ + ListMapEntry entry = entries.get(i); + if (keyEq(entry.key, key)) + return entry.value; + } + return null; + } + + public V put(K key, V 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 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(key, value)); + return null; + } + + public V remove(Object key) { + if (key == null) + throw new IllegalArgumentException(); + + for (int i = 0; i < entries.size(); i++){ + ListMapEntry entry = entries.get(i); + if (keyEq(entry.key, key)){ + return entries.remove(i).value; + } + } + return null; + } + + public void putAll(Map map) { + if (map instanceof ListMap){ + ListMap listMap = (ListMap) map; + ArrayList> otherEntries = listMap.entries; + for (int i = 0; i < otherEntries.size(); i++){ + ListMapEntry entry = otherEntries.get(i); + put(entry.key, entry.value); + } + }else{ + for (Map.Entry entry : map.entrySet()){ + put(entry.getKey(), entry.getValue()); + } + } + + } + + public void clear() { + entries.clear(); + } + + @Override + public ListMap clone(){ + ListMap clone = new ListMap(size()); + clone.putAll(this); + return clone; + } + + public Set keySet() { + HashSet keys = new HashSet(); + for (int i = 0; i < entries.size(); i++){ + ListMapEntry entry = entries.get(i); + keys.add(entry.key); + } + return keys; + } + + public Collection values() { + ArrayList values = new ArrayList(); + for (int i = 0; i < entries.size(); i++){ + ListMapEntry entry = entries.get(i); + values.add(entry.value); + } + return values; + } + + public Set> entrySet() { + HashSet> entryset = new HashSet>(); + 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 000000000..6834d9a45 --- /dev/null +++ b/engine/src/core/com/jme3/util/LittleEndien.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.BufferedInputStream; +import java.io.BufferedReader; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * LittleEndien 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 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 final int readUnsignedShort() throws IOException{ + return (in.read()&0xff) | ((in.read()&0xff) << 8); + } + + /** + * read an unsigned int as a long + */ + public final long readUInt() throws IOException{ + return ((in.read()&0xff) | + ((in.read()&0xff) << 8) | + ((in.read()&0xff) << 16) | + (((long)(in.read()&0xff)) << 24) + ); + } + + public final boolean readBoolean() throws IOException{ + return (in.read()!=0); + } + + public final byte readByte() throws IOException{ + return (byte) in.read(); + } + + public final int readUnsignedByte() throws IOException{ + return in.read(); + } + + public final short readShort() throws IOException{ + return (short) this.readUnsignedShort(); + } + + public final char readChar() throws IOException{ + return (char) this.readUnsignedShort(); + } + public final int readInt() throws IOException{ + return ( + (in.read()&0xff) | + ((in.read()&0xff) << 8) | + ((in.read()&0xff) << 16) | + ((in.read()&0xff) << 24) + ); + } + + public final 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 final float readFloat() throws IOException{ + return Float.intBitsToFloat(readInt()); + } + + public final double readDouble() throws IOException{ + return Double.longBitsToDouble(readLong()); + } + + public final void readFully(byte b[]) throws IOException{ + in.read(b, 0, b.length); + } + + public final void readFully(byte b[], int off, int len) throws IOException{ + in.read(b, off, len); + } + + public final int skipBytes(int n) throws IOException{ + return (int) in.skip(n); + } + + public final String readLine() throws IOException{ + return inRead.readLine(); + } + + public final String readUTF() throws IOException{ + throw new IOException("Unsupported operation"); + } + + public final void close() throws IOException{ + in.close(); + } + + public final int available() throws IOException{ + return in.available(); + } +} \ No newline at end of file diff --git a/engine/src/core/com/jme3/util/Screenshots.java b/engine/src/core/com/jme3/util/Screenshots.java new file mode 100644 index 000000000..5d75f740c --- /dev/null +++ b/engine/src/core/com/jme3/util/Screenshots.java @@ -0,0 +1,51 @@ +package com.jme3.util; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; + +public final class Screenshots { + 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/core/com/jme3/util/SkyFactory.java b/engine/src/core/com/jme3/util/SkyFactory.java new file mode 100644 index 000000000..8e0266cf7 --- /dev/null +++ b/engine/src/core/com/jme3/util/SkyFactory.java @@ -0,0 +1,90 @@ +package com.jme3.util; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +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.Texture; +import com.jme3.texture.TextureCubeMap; + +public class SkyFactory { + + private static final Sphere sphereMesh = new Sphere(10, 10, 10, false, true); + + public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap){ + Geometry sky = new Geometry("Sky", sphereMesh); + sky.setQueueBucket(Bucket.Sky); + sky.setCullHint(Spatial.CullHint.Never); + + Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); + skyMat.setTexture("Texture", texture); + skyMat.setVector3("NormalScale", normalScale); + if (sphereMap){ + skyMat.setBoolean("SphereMap", sphereMap); + } + sky.setMaterial(skyMat); + + return sky; + } + + public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale){ + Geometry sky = new Geometry("Sky", sphereMesh); + sky.setQueueBucket(Bucket.Sky); + sky.setCullHint(Spatial.CullHint.Never); + + Image westImg = west.getImage(); + Image eastImg = east.getImage(); + Image northImg = north.getImage(); + Image southImg = south.getImage(); + Image upImg = up.getImage(); + Image downImg = down.getImage(); + + 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)); + + 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 000000000..4878e506c --- /dev/null +++ b/engine/src/core/com/jme3/util/SortUtil.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 +{ + public static void gsort(Object[] a, Comparator comp){ + 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 ic){ + for (int i = 0; i < 1000000; i++){ + System.arraycopy(original, 0, sorted, 0, original.length); + gsort(sorted, ic); + } + + for (int i = 0; i < 1000000; i++){ + System.arraycopy(original, 0, sorted, 0, original.length); + qsort(sorted, ic); + } + + for (int i = 0; i < 1000000; i++){ + System.arraycopy(original, 0, sorted, 0, original.length); + msort(original, sorted, ic); + } + + for (int i = 0; i < 1000000; i++){ + System.arraycopy(original, 0, sorted, 0, original.length); + Arrays.sort(sorted, ic); + } + } + + public static void main(String[] args){ + Comparator ic = new Comparator() { + 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 sorts the supplied array using the specified comparator. + * + * @param src contains the elements to be sorted. + * @param dest must contain the same values as the src array. + */ + public static void msort (Object[] src, Object[] dest, Comparator comp) + { + msort(src, dest, 0, src.length, 0, comp); + } + + /** + * Merge sorts the supplied array using the specified comparator. + * + * @param src contains the elements to be sorted. + * @param dest must contain the same values as the src array. + */ + public static void msort (Object[] src, Object[] dest, int low, int high, + Comparator comp) + { + msort(src, dest, low, high, 0, comp); + } + + /** Implements the actual merge sort. */ + @SuppressWarnings("unchecked") + protected static void msort (Object[] src, Object[] dest, int low, + int high, int offset, Comparator comp) + { + // use an insertion sort on small arrays + int length = high - low; + if (length < INSERTION_SORT_THRESHOLD) { + for (int ii = low; ii < high; ii++) { + for (int jj = ii; + jj > low && comp.compare(dest[jj-1], dest[jj]) > 0; jj--) { + Object temp = dest[jj]; + dest[jj] = dest[jj-1]; + dest[jj-1] = temp; + } + } + return; + } + + // recursively sort each half of dest into src + int destLow = low, destHigh = high; + low += offset; + high += offset; + int mid = (low + high) >> 1; + msort(dest, src, low, mid, -offset, comp); + msort(dest, src, mid, high, -offset, comp); + + // if the list is already sorted, just copy from src to dest; this + // optimization results in faster sorts for nearly ordered lists + if (comp.compare(src[mid-1], src[mid]) <= 0) { + System.arraycopy(src, low, dest, destLow, length); + return; + } + + // merge the sorted halves (now in src) into dest + for (int ii = destLow, pp = low, qq = mid; ii < destHigh; ii++) { + if (qq >= high || pp < mid && comp.compare(src[pp], src[qq]) <= 0) { + dest[ii] = src[pp++]; + } else { + dest[ii] = src[qq++]; + } + } + } + + /** The size at or below which we will use insertion sort because it's + * probably faster. */ + private static final int INSERTION_SORT_THRESHOLD = 7; +} 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 000000000..b796fa46a --- /dev/null +++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import static com.jme3.util.BufferUtils.*; + +/** + * @author Lex + */ +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 interface IndexWrapper { + public int get(int i); + public int size(); + } + + private static IndexWrapper getIndexWrapper(final Buffer buff) { + if (buff instanceof ShortBuffer) { + return new IndexWrapper() { + private ShortBuffer buf = (ShortBuffer) buff; + public int get(int i) { + return ((int) buf.get(i))&(0x0000FFFF); + } + public int size() { + return buf.capacity(); + } + }; + } + else if (buff instanceof IntBuffer) { + return new IndexWrapper() { + private IntBuffer buf = (IntBuffer) buff; + public int get(int i) { + return buf.get(i); + } + public int size() { + return buf.capacity(); + } + }; + } else { + throw new IllegalArgumentException(); + } + } + + private static class VertexData { + public final Vector3f tangent = new Vector3f(); + public final Vector3f binormal = new Vector3f(); + public final List triangles = + new ArrayList(); + + public VertexData() { + + } + } + + public static class TriangleData { + public final Vector3f tangent; + public final Vector3f binormal; + public final Vector3f normal; + public int index0; + public int index1; + public int index2; + + public TriangleData(Vector3f tangent, Vector3f binormal, + Vector3f normal, + int index0, int index1, int index2) + { + this.tangent = tangent; + this.binormal = binormal; + this.normal = normal; + + this.index0 = index0; + this.index1 = index1; + this.index2 = index2; + } + } + + 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(); + } + + 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); + } + + private static VertexData[] processTriangles(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t) + { + IndexWrapper indexBuffer = getIndexWrapper(mesh.getBuffer(Type.Index).getData()); + 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) + { + IndexWrapper indexBuffer = getIndexWrapper(mesh.getBuffer(Type.Index).getData()); + 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) + { + IndexWrapper indexBuffer = getIndexWrapper(mesh.getBuffer(Type.Index).getData()); + 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, + index[0], index[1], index[2] + ); + } + + 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 void processTriangleData(Mesh mesh, VertexData[] vertices, + boolean approxTangent) + { + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + + FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 3); + 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 i = 0; i < vertices.length; i++) { + + populateFromBuffer(givenNormal, normalBuffer, i); + givenNormal.normalizeLocal(); + + VertexData currentVertex = vertices[i]; + List triangles = currentVertex.triangles; + + // check tangent and binormal consistency + tangent.set(triangles.get(0).tangent); + tangent.normalizeLocal(); + binormal.set(triangles.get(0).binormal); + binormal.normalizeLocal(); + + for (int j = 1; 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); + + 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); + } + + if (tangent.length() < ZERO_TOLERANCE) { +// log.log(Level.WARNING, +// "Shared tangent is zero for vertex {0}.", i); + // 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(triangles.get(0).tangent); + } + } + else { + tangent.divideLocal(triangles.size()); + } + + 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}.", i); + } + + + if (!approxTangent) { + + if (binormal.length() < ZERO_TOLERANCE) { +// log.log(Level.WARNING, +// "Shared binormal is zero for vertex {0}.", i); + // 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(triangles.get(0).binormal); + } + } + else { + binormal.divideLocal(triangles.size()); + } + + 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}.", i); + } + + if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) + < ZERO_TOLERANCE) + { +// log.log(Level.WARNING, +// "Tangent and binormal are parallel for vertex {0}.", i); + } + } + + if (approxTangent) { + givenNormal.cross(tangent, binormal); + binormal.cross(givenNormal, tangent); + tangent.normalizeLocal(); + + setInBuffer(tangent, tangents, i); + } + else { + setInBuffer(tangent, tangents, i); + setInBuffer(binormal, binormals, i); + } + } + + mesh.setBuffer(Type.Tangent, 3, 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); + + for (int i = 0; i < vertexBuffer.capacity() / 3; i++) { + populateFromBuffer(origin, vertexBuffer, i); + populateFromBuffer(normal, normalBuffer, i); + 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); + + if (binormalBuffer == null) { + normal.cross(tangent, point); + 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 000000000..ebd828a3d --- /dev/null +++ b/engine/src/core/com/jme3/util/TempVars.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.Eigen3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Matrix3f; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Triangle; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +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(). A method using temp vars with this + * class is not allowed to make calls to other methods using the class otherwise + * memory corruption will occur. A locking mechanism may be implemented + * in the future to prevent the occurance of such situation. + */ +public class TempVars { + + private static final ThreadLocal varsLocal + = new ThreadLocal(){ + @Override + public TempVars initialValue(){ + return new TempVars(); + } + }; + + public static TempVars get(){ + return varsLocal.get(); + } + + private TempVars(){ + } + + private boolean locked = false; + private StackTraceElement[] lockerStack; + + public final boolean lock(){ + if (locked){ + System.err.println("INTERNAL ERROR"); + System.err.println("Offending trace: "); + + StackTraceElement[] stack = new Throwable().getStackTrace(); + for (int i = 1; i < stack.length; i++){ + System.err.println("\tat "+stack[i].toString()); + } + + System.err.println("Attempted to aquire TempVars lock owned by"); + for (int i = 1; i < lockerStack.length; i++){ + System.err.println("\tat "+lockerStack[i].toString()); + } + System.exit(1); + return false; + } + + lockerStack = new Throwable().getStackTrace(); + locked = true; + return true; + } + + public final boolean unlock(){ + if (!locked){ + System.err.println("INTERNAL ERROR"); + System.err.println("Attempted to release non-existent lock: "); + + StackTraceElement[] stack = new Throwable().getStackTrace(); + for (int i = 1; i < stack.length; i++){ + System.err.println("\tat "+stack[i].toString()); + } + + System.exit(1); + return false; + } + + lockerStack = null; + locked = false; + return true; + } + + /** + * 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]; + + /** + * Fetching triangle from mesh + */ + public final Triangle triangle = new Triangle(); + + /** + * 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(); + public final Vector3f vect8 = new Vector3f(); + public final Vector3f vect9 = new Vector3f(); + public final Vector3f vect10 = new Vector3f(); + + 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(); + + /** + * General quaternions. + */ + public final Quaternion quat1 = 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]; + + /** + * BIHTree + */ + public final float[] bihSwapTmp = new float[9]; + public final ArrayList bihStack = new ArrayList(); + +} 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 000000000..51a618284 --- /dev/null +++ b/engine/src/core/com/jme3/util/xml/SAXUtil.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.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 { + + 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 if (bool.equals("false")) + return false; + else if (bool.equals("true")) + return true; + 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-fx/com/jme3/post/HDRConfig.form b/engine/src/desktop-fx/com/jme3/post/HDRConfig.form new file mode 100644 index 000000000..438de32e8 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/HDRConfig.form @@ -0,0 +1,126 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/engine/src/desktop-fx/com/jme3/post/HDRConfig.java b/engine/src/desktop-fx/com/jme3/post/HDRConfig.java new file mode 100644 index 000000000..6d5002f8f --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/HDRConfig.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +public class HDRConfig extends javax.swing.JFrame { + + private float gamma, a, white; + private int lod; + private HDRRenderer hdrRender; + + public HDRConfig(HDRRenderer hdrRender) { + initComponents(); + this.hdrRender = hdrRender; + sldWhiteStateChanged(null); + sldGammaStateChanged(null); + sldLODStateChanged(null); + jComboBox1ActionPerformed(null); + } + + public float getA() { + return a; + } + + public float getGamma() { + return gamma; + } + + public int getLod() { + return lod; + } + + public float getWhite() { + return white; + } + + private void updateHdr(){ + hdrRender.setWhiteLevel(getWhite()); + hdrRender.setThrottle(getLod()); + hdrRender.setExposure(getA()); + } + + /** 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + pnlBright = new javax.swing.JPanel(); + lblWhite = new javax.swing.JLabel(); + sldWhite = new javax.swing.JSlider(); + pnlGamma = new javax.swing.JPanel(); + lblGamma = new javax.swing.JLabel(); + sldGamma = new javax.swing.JSlider(); + pnlA = new javax.swing.JPanel(); + lblA = new javax.swing.JLabel(); + jComboBox1 = new javax.swing.JComboBox(); + pnlLOD = new javax.swing.JPanel(); + lblLOD = new javax.swing.JLabel(); + sldLOD = new javax.swing.JSlider(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle("HDR Configuration"); + getContentPane().setLayout(new javax.swing.BoxLayout(getContentPane(), javax.swing.BoxLayout.Y_AXIS)); + + lblWhite.setText("Maximum Brightness: "); + pnlBright.add(lblWhite); + + sldWhite.setMaximum(1000); + sldWhite.setValue(100); + sldWhite.setPreferredSize(new java.awt.Dimension(100, 23)); + sldWhite.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + sldWhiteStateChanged(evt); + } + }); + pnlBright.add(sldWhite); + + getContentPane().add(pnlBright); + + lblGamma.setText("Output Gamma: "); + pnlGamma.add(lblGamma); + + sldGamma.setMaximum(500); + sldGamma.setMinimum(1); + sldGamma.setValue(100); + sldGamma.setPreferredSize(new java.awt.Dimension(100, 23)); + sldGamma.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + sldGammaStateChanged(evt); + } + }); + pnlGamma.add(sldGamma); + + getContentPane().add(pnlGamma); + + lblA.setText("Exposure: "); + pnlA.add(lblA); + + jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Dark", "Dim", "Normal", "Light" })); + jComboBox1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jComboBox1ActionPerformed(evt); + } + }); + pnlA.add(jComboBox1); + + getContentPane().add(pnlA); + + lblLOD.setText("Leve of detail: "); + pnlLOD.add(lblLOD); + + sldLOD.setMaximum(15); + sldLOD.setMinimum(1); + sldLOD.setValue(1); + sldLOD.setPreferredSize(new java.awt.Dimension(100, 23)); + sldLOD.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + sldLODStateChanged(evt); + } + }); + pnlLOD.add(sldLOD); + + getContentPane().add(pnlLOD); + + pack(); + }// //GEN-END:initComponents + + private void sldWhiteStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldWhiteStateChanged + white = sldWhite.getValue() / 100f; + updateHdr(); + }//GEN-LAST:event_sldWhiteStateChanged + + private void sldGammaStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldGammaStateChanged + gamma = sldGamma.getValue() / 100f; + updateHdr(); + }//GEN-LAST:event_sldGammaStateChanged + + private void sldLODStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldLODStateChanged + lod = sldLOD.getValue(); + updateHdr(); + }//GEN-LAST:event_sldLODStateChanged + + private void jComboBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jComboBox1ActionPerformed + switch (jComboBox1.getSelectedIndex()){ + case 0: a = 0.09f; break; + case 1: a = 0.18f; break; + case 2: a = 0.36f; break; + case 3: a = 0.72f; break; + } + updateHdr(); + }//GEN-LAST:event_jComboBox1ActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox jComboBox1; + private javax.swing.JLabel lblA; + private javax.swing.JLabel lblGamma; + private javax.swing.JLabel lblLOD; + private javax.swing.JLabel lblWhite; + private javax.swing.JPanel pnlA; + private javax.swing.JPanel pnlBright; + private javax.swing.JPanel pnlGamma; + private javax.swing.JPanel pnlLOD; + private javax.swing.JSlider sldGamma; + private javax.swing.JSlider sldLOD; + private javax.swing.JSlider sldWhite; + // End of variables declaration//GEN-END:variables + +} diff --git a/engine/src/desktop-fx/com/jme3/post/HDRRenderer.java b/engine/src/desktop-fx/com/jme3/post/HDRRenderer.java new file mode 100644 index 000000000..cdec71691 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/HDRRenderer.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.post.SceneProcessor; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.renderer.Renderer; +import com.jme3.asset.AssetManager; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +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; +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 = 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 = 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.Luminance8; + 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/desktop-fx/com/jme3/post/filters/BloomFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/BloomFilter.java new file mode 100644 index 000000000..c956a4d8d --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/BloomFilter.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.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.Renderer; +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 + * "soap opera" glow effect. + * + * @author Nehon + */ +public class BloomFilter extends Filter { + + /** + * GlowMode specifies if bright objects or objects + * with glow map will be bloomed. + */ + public enum GlowMode { + + /** + * Apply bloom filter to bright objects in the scene. + */ + Scene, + /** + * Apply bloom only to objects that have a glow map. + */ + Objects, + /** + * Apply bloom to both bright objects and objects with glow map. + */ + SceneAndObjects; + } + /**@deprecated use {@link GlowMode} enum */ + public static final int GLOW_SCENE = 0; + /**@deprecated use {@link GlowMode} enum */ + public static final int GLOW_OBJECTS = 1; + /**@deprecated use {@link GlowMode} enum */ + public static final int GLOW_BOTH = 2; + /**@deprecated use GLOW_SCENE instead*/ + public static final int GLOW_MODE_ONLY_EXTRACTED_LIGHTS = 0; + /**@deprecated use GLOW_OBJECTS instead*/ + public static final int GLOW_MODE_ONLY_GLOW_OBJECTS = 1; + /**@deprecated use GLOW_BOTH instead*/ + public static final int GLOW_MODE_BOTH = 2; + 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; + private ColorRGBA backupColor; + + /** + * creates a Bloom filter + */ + public BloomFilter() { + super("BloomFilter"); + } + + /** + * Crete the bloom filter with the specific glow mode + * @param glowMode + */ + public BloomFilter(GlowMode glowMode) { + this(); + this.glowMode = glowMode; + } + + /** + * + * @param width + * @param height + * @deprecated use BloomFilter() instead + */ + @Deprecated + public BloomFilter(int width, int height) { + super("BloomFilter"); + screenWidth = width; + screenHeight = height; + } + + /** + * + * @param width + * @param height + * @param glowMode + * @deprecated use BloomFilter(GlowMode glowMode) instead + */ + @Deprecated + public BloomFilter(int width, int height, GlowMode glowMode) { + this(width, height); + this.glowMode = glowMode; + } + + @Override + public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + screenWidth = (int) (w / downSamplingFactor); + screenHeight = (int) (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(); + //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 + public void cleanUpFilter(Renderer r) { + + if (preGlowPass != null) { + preGlowPass.cleanup(r); + } + if (extractPass != null) { + extractPass.cleanup(r); + } + if (horizontalBlur != null) { + horizontalBlur.cleanup(r); + } + if (verticalalBlur != null) { + verticalalBlur.cleanup(r); + } + } + + @Override + public Material getMaterial() { + material.setFloat("BloomIntensity", bloomIntensity); + return material; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + if (glowMode != GlowMode.Scene) { + backupColor = viewPort.getBackgroundColor(); + viewPort.setBackgroundColor(ColorRGBA.Black); + renderManager.getRenderer().setFrameBuffer(preGlowPass.getRenderFrameBuffer()); + renderManager.getRenderer().clearBuffers(true, true, true); + renderManager.setForcedTechnique("Glow"); + renderManager.renderViewPortQueues(viewPort, false); + viewPort.setBackgroundColor(backupColor); + renderManager.setForcedTechnique(null); + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + } + } + + public float getBloomIntensity() { + return bloomIntensity; + } + + /** + * intensity of the bloom effect + * @param bloomIntensity + */ + public void setBloomIntensity(float bloomIntensity) { + this.bloomIntensity = bloomIntensity; + } + + public float getBlurScale() { + return blurScale; + } + + /** + * The spread of the bloom + * @param blurScale + */ + public void setBlurScale(float blurScale) { + this.blurScale = blurScale; + } + + 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; + } + + public float getExposurePower() { + return exposurePower; + } + + /** + * the power of the bloomed color + * @param exposurePower + */ + public void setExposurePower(float exposurePower) { + this.exposurePower = exposurePower; + } + + /** + * returns the downSampling factor + * @return + */ + public float getDownSamplingFactor() { + return downSamplingFactor; + } + + /** + * Sets the downSampling factor : the size of the computed texture will be divided by this factor. + * 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/desktop-fx/com/jme3/post/filters/CartoonEdgeFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/CartoonEdgeFilter.java new file mode 100644 index 000000000..d861530e4 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/CartoonEdgeFilter.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 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); + + public CartoonEdgeFilter() { + super("CartoonEdgeFilter"); + } + + @Override + public boolean isRequiresDepthTexture() { + return true; + } + + @Override + public void preRender(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 + public Material getMaterial() { + material.setTexture("NormalsTexture", normalPass.getRenderedTexture()); + return material; + } + + @Override + public 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + if (normalPass != null) { + normalPass.cleanup(r); + } + } + + public float getDepthSensitivity() { + return depthSensitivity; + } + + public void setDepthSensitivity(float depthSensitivity) { + this.depthSensitivity = depthSensitivity; + if (material != null) { + material.setFloat("DepthSensitivity", depthSensitivity); + } + } + + public float getDepthThreshold() { + return depthThreshold; + } + + public void setDepthThreshold(float depthThreshold) { + this.depthThreshold = depthThreshold; + if (material != null) { + material.setFloat("DepthThreshold", depthThreshold); + } + } + + public float getEdgeIntensity() { + return edgeIntensity; + } + + public void setEdgeIntensity(float edgeIntensity) { + this.edgeIntensity = edgeIntensity; + if (material != null) { + material.setFloat("EdgeIntensity", edgeIntensity); + } + } + + public float getEdgeWidth() { + return edgeWidth; + } + + public void setEdgeWidth(float edgeWidth) { + this.edgeWidth = edgeWidth; + if (material != null) { + material.setFloat("EdgeWidth", edgeWidth); + } + + } + + public float getNormalSensitivity() { + return normalSensitivity; + } + + public void setNormalSensitivity(float normalSensitivity) { + this.normalSensitivity = normalSensitivity; + if (material != null) { + material.setFloat("NormalSensitivity", normalSensitivity); + } + } + + public float getNormalThreshold() { + return 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/desktop-fx/com/jme3/post/filters/ColorOverlayFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/ColorOverlayFilter.java new file mode 100644 index 000000000..acd9f3e08 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/ColorOverlayFilter.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.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.Renderer; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * + * @author nehon + */ +public class ColorOverlayFilter extends Filter { + + private ColorRGBA color = ColorRGBA.White; + + public ColorOverlayFilter() { + super("Color Overlay"); + } + + public ColorOverlayFilter(ColorRGBA color) { + this(); + this.color = color; + } + + @Override + public Material getMaterial() { + + material.setColor("Color", color); + return material; + } + + public ColorRGBA getColor() { + return color; + } + + public void setColor(ColorRGBA color) { + this.color = color; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + } + + @Override + public 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + } +} diff --git a/engine/src/desktop-fx/com/jme3/post/filters/DepthOfFieldFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/DepthOfFieldFilter.java new file mode 100644 index 000000000..f82858691 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/DepthOfFieldFilter.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.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.post.Filter; +import com.jme3.material.Material; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +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; + + public DepthOfFieldFilter() { + super("Depth Of Field"); + } + + @Override + public boolean isRequiresDepthTexture() { + return true; + } + + @Override + public Material getMaterial() { + + return material; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + } + + @Override + public 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + } + + /** + * 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); + } + + } + + 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); + } + + } + + 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); + } + } + + public float getBlurScale() { + return blurScale; + } +} diff --git a/engine/src/desktop-fx/com/jme3/post/filters/FadeFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/FadeFilter.java new file mode 100644 index 000000000..5d7023533 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/FadeFilter.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.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.Renderer; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * + * @author Nehon + * implemented from boxjar implementation + * see http://jmonkeyengine.org/groups/graphics/forum/topic/newbie-question-general-fade-inout-effect/#post-105559 + */ +public class FadeFilter extends Filter { + + private float value = 1; + private boolean playing = false; + private float direction = 1; + private float duration = 1; + + public FadeFilter() { + super("Fade In/Out"); + } + + public FadeFilter(float duration) { + this(); + this.duration = duration; + } + + @Override + public Material getMaterial() { + material.setFloat("Value", value); + return material; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + } + + @Override + public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/Fade.j3md"); + } + + @Override + public void preFrame(float tpf) { + if (playing) { + value += tpf * direction / duration; + + if (direction > 0 && value > 1) { + value = 1; + playing = false; + processor.setFilterEnabled(this, false); + } + if (direction < 0 && value < 0) { + value = 0; + playing = false; + processor.setFilterEnabled(this, false); + } + } + } + + public float getDuration() { + return duration; + } + + public void setDuration(float duration) { + this.duration = duration; + } + + public void fadeIn() { + processor.setFilterEnabled(this, true); + direction = 1; + playing = true; + } + + public void fadeOut() { + processor.setFilterEnabled(this, true); + direction = -1; + playing = true; + + } + + @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); + } + + public float getValue() { + return value; + } + + public void setValue(float value) { + this.value = value; + } + + @Override + public void cleanUpFilter(Renderer r) { + } +} diff --git a/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.java new file mode 100644 index 000000000..d29f8c9eb --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.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.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.Renderer; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * A filter to render a fog effect + * @author Nehon + */ +public class FogFilter extends Filter { + + private ColorRGBA fogColor = ColorRGBA.White.clone(); + private float fogDensity = 0.7f; + private float fogDistance = 1000; + + public FogFilter() { + super("FogFilter"); + } + + public FogFilter(ColorRGBA fogColor, float fogDensity, float fogDistance) { + this(); + this.fogColor = fogColor; + this.fogDensity = fogDensity; + this.fogDistance = fogDistance; + } + + @Override + public boolean isRequiresDepthTexture() { + return true; + } + + @Override + public 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 + public Material getMaterial() { + + return material; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + } + + /** + * 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 fogColor + */ + 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + } +} diff --git a/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.java new file mode 100644 index 000000000..89ccfc4df --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.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.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.Renderer; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * + * @author 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; + private float innerLightDensity; + + public LightScatteringFilter() { + super("Light Scattering"); + } + + public LightScatteringFilter(Vector3f lightPosition) { + this(); + this.lightPosition = lightPosition; + } + + @Override + public boolean isRequiresDepthTexture() { + return true; + } + + @Override + public 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 + public void preRender(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); + } + } + + public 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 + public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/LightScattering.j3md"); + } + + public float getBlurStart() { + return blurStart; + } + + public void setBlurStart(float blurStart) { + this.blurStart = blurStart; + } + + public float getBlurWidth() { + return blurWidth; + } + + public void setBlurWidth(float blurWidth) { + this.blurWidth = blurWidth; + } + + public float getLightDensity() { + return lightDensity; + } + + public void setLightDensity(float lightDensity) { + this.lightDensity = lightDensity; + } + + public Vector3f getLightPosition() { + return lightPosition; + } + + public void setLightPosition(Vector3f lightPosition) { + this.lightPosition = lightPosition; + } + + public int getNbSamples() { + return 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + } +} diff --git a/engine/src/desktop-fx/com/jme3/post/filters/RadialBlurFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/RadialBlurFilter.java new file mode 100644 index 000000000..3f1f090c9 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/filters/RadialBlurFilter.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.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.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.shader.VarType; +import java.io.IOException; + +/** + * + * @author 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}; + + public RadialBlurFilter() { + super("Radial blur"); + } + + public RadialBlurFilter(float sampleDist, float sampleStrength) { + this(); + this.sampleDist = sampleDist; + this.sampleStrength = sampleStrength; + } + + @Override + public Material getMaterial() { + + material.setFloat("SampleDist", sampleDist); + material.setFloat("SampleStrength", sampleStrength); + material.setParam("Samples", VarType.FloatArray, samples); + + return material; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + } + + public float getSampleDist() { + return sampleDist; + } + + public void setSampleDist(float sampleDist) { + this.sampleDist = sampleDist; + } + + public float getSampleStrength() { + return sampleStrength; + } + + public void setSampleStrength(float sampleStrength) { + this.sampleStrength = sampleStrength; + } + + @Override + public 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + } +} diff --git a/engine/src/desktop-fx/com/jme3/post/ssao/SSAOFilter.java b/engine/src/desktop-fx/com/jme3/post/ssao/SSAOFilter.java new file mode 100644 index 000000000..63a323389 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/post/ssao/SSAOFilter.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +/** + * + * @author 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 int downSampleFactor = 1; + + /** + * Create a Screen Space Ambiant Occlusion Filter + */ + public SSAOFilter() { + super("SSAOFilter"); + } + + /** + * + * @param vp + * @param sampleRadius + * @param intensity + * @param scale + * @param bias + */ + public SSAOFilter(float sampleRadius, float intensity, float scale, float bias) { + this(); + this.sampleRadius = sampleRadius; + this.intensity = intensity; + this.scale = scale; + this.bias = bias; + } + + @Override + public boolean isRequiresDepthTexture() { + return true; + } + + @Override + public void preRender(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 + public Material getMaterial() { + return material; + } + + @Override + public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + int screenWidth = w; + int screenHeight = h; + postRenderPasses = new ArrayList(); + + normalPass = new Pass(); + normalPass.init(renderManager.getRenderer(), screenWidth / downSampleFactor, 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 downSampleFactor == 1; + } + }; + + ssaoPass.init(renderManager.getRenderer(), screenWidth / downSampleFactor, 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 = 2.0f; + material.setFloat("XScale", blurScale * xScale); + material.setFloat("YScale", blurScale * yScale); + + } + + public float getBias() { + return bias; + } + + public void setBias(float bias) { + this.bias = bias; + if (ssaoMat != null) { + ssaoMat.setFloat("Bias", bias); + } + } + + public float getIntensity() { + return intensity; + } + + public void setIntensity(float intensity) { + this.intensity = intensity; + if (ssaoMat != null) { + ssaoMat.setFloat("Intensity", intensity); + } + + } + + public float getSampleRadius() { + return sampleRadius; + } + + public void setSampleRadius(float sampleRadius) { + this.sampleRadius = sampleRadius; + if (ssaoMat != null) { + ssaoMat.setFloat("SampleRadius", sampleRadius); + } + + } + + public float getScale() { + return scale; + } + + public void setScale(float scale) { + this.scale = scale; + if (ssaoMat != null) { + ssaoMat.setFloat("Scale", scale); + } + + } + + public boolean isUseAo() { + return useAo; + } + + public void setUseAo(boolean useAo) { + this.useAo = useAo; + if (material != null) { + material.setBoolean("UseAo", useAo); + } + + } + + public boolean isUseOnlyAo() { + return useOnlyAo; + } + + 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); + } + + @Override + public void cleanUpFilter(Renderer r) { + } +} diff --git a/engine/src/desktop-fx/com/jme3/shadow/BasicShadowRenderer.java b/engine/src/desktop-fx/com/jme3/shadow/BasicShadowRenderer.java new file mode 100644 index 000000000..92d28212f --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/shadow/BasicShadowRenderer.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.shadow; + +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.asset.AssetManager; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; + +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(); + + 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; + } + + public Vector3f getDirection() { + return direction; + } + + public void setDirection(Vector3f direction) { + this.direction.set(direction).normalizeLocal(); + } + + public Vector3f[] getPoints() { + return points; + } + + 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.setDirection(direction); + 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); + } + + 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/desktop-fx/com/jme3/shadow/PssmShadowRenderer.java b/engine/src/desktop-fx/com/jme3/shadow/PssmShadowRenderer.java new file mode 100644 index 000000000..5e4e681e0 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/shadow/PssmShadowRenderer.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.shadow; + +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.asset.AssetManager; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.OpaqueComparator; +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; + +public class PssmShadowRenderer implements SceneProcessor { + + @Deprecated + public static final String EDGE_FILTERING_PCF = "EDGE_FILTERING_PCF"; + + @Deprecated + public static final String EDGE_FILTERING_DITHER = "EDGE_FILTERING_DITHER"; + + @Deprecated + public enum FILTERING { + PCF4X4, + PCF8X8, + PCF10X10, + PCF16X16, + PCF20X20 + } + + /** + * FilterMode 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 + } + + 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]; + + /** + * Create a PSSM Shadow Renderer + * More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html + * @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). + */ + public PssmShadowRenderer(AssetManager manager, int size, int nbSplits) { + 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"); + postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md"); + + 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); + + shadowCam = new Camera(size, size); + shadowCam.setParallelProjection(true); + + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + } + + @Deprecated + public PssmShadowRenderer(AssetManager manager, int size, int nbSplits, String filterMode){ + this(manager, size, nbSplits); + if (filterMode.equals(EDGE_FILTERING_DITHER)){ + setFilterMode(FilterMode.Dither); + }else if (filterMode.equals(EDGE_FILTERING_PCF)){ + setFilterMode(FilterMode.PCF4); + } + } + + 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); + } + } + } + } + + 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); + frustumMdl.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md")); + 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; + } + + public Vector3f getDirection() { + return direction; + } + + public void setDirection(Vector3f direction) { + this.direction.set(direction).normalizeLocal(); + } + + 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(); + // zFar = PssmShadowUtil.computeZFar(occluders, receivers, viewCam); + } + // System.out.println("Zfar : "+zFar); + ShadowUtil.updateFrustumPoints(viewCam, viewCam.getFrustumNear(), zFar, 1.0f, points); + +// Vector3f frustaCenter = new Vector3f(); +// for (Vector3f point : points) { +// frustaCenter.addLocal(point); +// } +// frustaCenter.multLocal(1f / 8f); + + //shadowCam.setDirection(direction); + shadowCam.getRotation().lookAt(direction, shadowCam.getUp()); + shadowCam.update(); + shadowCam.updateViewProjection(); + + PssmShadowUtil.updateFrustumSplits(splitsArray, viewCam.getFrustumNear(), 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 + // if(cropShadows){ + ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders); +// }else{ +// ShadowUtil.updateShadowCamera(shadowCam, points); +// } + //displaying the current splitted frustrum and the associated cropped light frustrums in wireframe. + //only for debuging purpose +// if (debug) { +// viewPort.attachScene(createFrustum(points, i)); +// Vector3f[] pts = new Vector3f[8]; +// for (int j = 0; j < pts.length; j++) { +// pts[j] = new Vector3f(); +// } +// ShadowUtil.updateFrustumPoints2(shadowCam, pts); +// viewPort.attachScene(createFrustum(pts, i)); +// if (i == nbSplits-1) { +// debug = false; +// } +// } + + //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); + //viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, i == nbSplits - 1); + + } + 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 + public 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, true); + 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) { + } + + 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; + } + + 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; + } + + 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); + } + + public int getEdgesThickness() { + return (int) (edgesThickness * 10); + } + + public void setEdgesThickness(int edgesThickness) { + this.edgesThickness = Math.max(1, Math.min(edgesThickness,10)); + this.edgesThickness *= 0.1f; + postshadowMat.setFloat("PCFEdge", edgesThickness); + } + + @Deprecated + public FILTERING getPcfFilter() { + switch (filterMode){ + case PCF4: + return FILTERING.PCF4X4; + case PCF8: + return FILTERING.PCF8X8; + default: + return null; + } + } + + @Deprecated + public void setPcfFilter(FILTERING pcfFilter) { + switch (pcfFilter){ + case PCF4X4: + setFilterMode(FilterMode.PCF4); + break; + case PCF8X8: + setFilterMode(FilterMode.PCF8); + break; + } + } + + +} + diff --git a/engine/src/desktop-fx/com/jme3/shadow/PssmShadowUtil.java b/engine/src/desktop-fx/com/jme3/shadow/PssmShadowUtil.java new file mode 100644 index 000000000..29f06aa3e --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/shadow/PssmShadowUtil.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.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 java.util.Arrays; + +import static java.lang.Math.*; + +/** + * Includes various useful shadow mapping functions. + * + * See: + * http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/ + * http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html + * for more info. + */ +public final class PssmShadowUtil { + + public static void main(String[] args){ + float[] splits = new float[5]; + float[] splitsShader = new float[3]; + updateFrustumSplits(splits, 1, 1000, 0.5f); + System.arraycopy(splits, 1, splitsShader, 0, splitsShader.length); + System.out.println(Arrays.toString(splitsShader)); + + for (int i = 0; i < splits.length-1; i++){ + System.out.println(splits[i] + " - " + splits[i+1]); + } + } + + /** + * Updates the frustum splits stores in splits 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/desktop-fx/com/jme3/shadow/ShadowCamera.java b/engine/src/desktop-fx/com/jme3/shadow/ShadowCamera.java new file mode 100644 index 000000000..8966dadd6 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/shadow/ShadowCamera.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.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; + +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 + */ + private void updateLightCamera(Camera lightCam){ + if (target.getType() == Light.Type.Directional){ + DirectionalLight dl = (DirectionalLight) target; + lightCam.setParallelProjection(true); + lightCam.setLocation(Vector3f.ZERO); + lightCam.setDirection(dl.getDirection()); + 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/desktop-fx/com/jme3/shadow/ShadowUtil.java b/engine/src/desktop-fx/com/jme3/shadow/ShadowUtil.java new file mode 100644 index 000000000..ba16f4fc5 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/shadow/ShadowUtil.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.FastMath; +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 java.util.ArrayList; +import java.util.List; + +import static java.lang.Math.*; + +/** + * Includes various useful shadow mapping functions. + * + * See: + * http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/ + * http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html + * for more info. + */ +public class ShadowUtil { + + 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); + } + } + } + + 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 (newVol.getCenter().x != Float.NaN && newVol.getCenter().x != Float.POSITIVE_INFINITY && newVol.getCenter().x != Float.NEGATIVE_INFINITY) { + bbox.mergeLocal(newVol); + } + } + return bbox; + } + + 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 (store.getCenter().x != Float.NaN && store.getCenter().x != Float.POSITIVE_INFINITY && store.getCenter().x != Float.NEGATIVE_INFINITY) { + bbox.mergeLocal(store); + } + } + return bbox; + } + + public static BoundingBox computeUnionBound(List bv) { + BoundingBox bbox = new BoundingBox(); + for (int i = 0; i < bv.size(); i++) { + BoundingVolume vol = bv.get(i); + bbox.mergeLocal(vol); + } + return bbox; + } + + 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); + } + + 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); + } + +// min.x = FastMath.clamp(min.x, -1f, 1f); +// max.y = FastMath.clamp(max.y, -1f, 1f); +// min.x = FastMath.clamp(min.x, -1f, 1f); +// max.y = FastMath.clamp(max.y, -1f, 1f); + + 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); + //return new BoundingBox(center, extent.x, extent.y, extent.z); + } + + /** + * Updates the shadow camera to properly contain the given + * points (which contain the eye camera frustum corners) + * + * @param occluders + * @param lightCam + * @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 lightCam + * @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 lightCam + * @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 + //Transform t = new Transform(shadowCam.getRotation()); + Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix(); + +// BoundingBox casterBB = computeUnionBound(occluders, viewProjMatrix); +// BoundingBox receiverBB = computeUnionBound(receivers, viewProjMatrix); + BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix); + + ArrayList visRecvList = new ArrayList(); + 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 visOccList = new ArrayList(); + 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); + +// actualMin.x = FastMath.clamp(actualMin.x, -1, 1); +// actualMin.y = FastMath.clamp(actualMin.y, -1, 1); +// actualMax.x = FastMath.clamp(actualMax.x, -1, 1); +// actualMax.y = FastMath.clamp(actualMax.y, -1, 1); +// float far = actualMin.z + actualMax.z * 4 + 1.0f + 1.5f; + 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); + +// cropMin.set(splitMin); +// cropMax.set(splitMax); + +// cropMin.z = Math.min(cropMin.z, cropMax.z - cropMin.z - 1000); +// cropMin.z = Math.max(10f, cropMin.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; + +// scaleZ = 2.0f / (cropMax.z - cropMin.z); +// offsetZ = -0.5f * (cropMax.z + cropMin.z) * scaleZ; + + Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX, + 0f, scaleY, 0f, offsetY, + 0f, 0f, scaleZ, offsetZ, + 0f, 0f, 0f, 1f); + +// cropMatrix.transposeLocal(); +// Matrix4f cropMatrix = new Matrix4f(); +// cropMatrix.setScale(new Vector3f(scaleX, scaleY, 1f)); +// cropMatrix.setTranslation(offsetX, offsetY, 0); + + Matrix4f result = new Matrix4f(); + result.set(cropMatrix); + result.multLocal(projMatrix); +// result.set(projMatrix); +// result.multLocal(cropMatrix); + shadowCam.setProjectionMatrix(result); + +// shadowCam.setFrustum(cropMin.z, cropMax.z, // near, far +// cropMin.x, cropMax.x, // left, right +// cropMax.y, cropMin.y); // top, bottom + + // compute size and center of final frustum + //float sizeX = (max.x - min.x) / 2f; + //float sizeY = (max.y - min.y) / 2f; + //float offsetX = (max.x + min.x) / -2f; + //float offsetY = (max.y + min.y) / -2f; + + // compute center for frustum + //temp.set(offsetX, offsetY, 0); + //invRot.mult(temp, temp); + + //shadowCam.setLocation(temp); + //shadowCam.setFrustum(min.z, max.z, -sizeX, sizeX, sizeY, -sizeY); + } +} diff --git a/engine/src/desktop-fx/com/jme3/water/ReflectionProcessor.java b/engine/src/desktop-fx/com/jme3/water/ReflectionProcessor.java new file mode 100644 index 000000000..1267d701f --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/water/ReflectionProcessor.java @@ -0,0 +1,93 @@ +/* + * 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; + + 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() { + } + + public FrameBuffer getReflectionBuffer() { + return reflectionBuffer; + } + + public void setReflectionBuffer(FrameBuffer reflectionBuffer) { + this.reflectionBuffer = reflectionBuffer; + } + + public Camera getReflectionCam() { + return reflectionCam; + } + + public void setReflectionCam(Camera reflectionCam) { + this.reflectionCam = reflectionCam; + } + + public Plane getReflectionClipPlane() { + return reflectionClipPlane; + } + + public void setReflectionClipPlane(Plane reflectionClipPlane) { + this.reflectionClipPlane = reflectionClipPlane; + } +} diff --git a/engine/src/desktop-fx/com/jme3/water/SimpleWaterProcessor.java b/engine/src/desktop-fx/com/jme3/water/SimpleWaterProcessor.java new file mode 100644 index 000000000..bbb89f57e --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/water/SimpleWaterProcessor.java @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +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.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; + +/** + * + * @author normenhansen + */ +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; + + 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; + + public void preFrame(float tpf) { + time = time + (tpf * speed); + if (time > 1f) { + time = 0; + } + material.setFloat("time", time); + } + + 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()); + reflectionCam.lookAt(targetLocation, Vector3f.UNIT_Y); + if (inv) { + reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal()); + } + } + + 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/gradient_map.jpg"); + 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 = rm.createPreView("Reflection View", reflectionCam); + reflectionView.setClearEnabled(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 = rm.createPreView("Refraction View", refractionCam); + refractionView.setClearEnabled(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; + } + + public int getRenderWidth() { + return renderWidth; + } + + 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; + } + + 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); + } + + public float getWaterDepth() { + return waterDepth; + } + + public float getWaterTransparency() { + return 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)); + } + + public boolean isDebug() { + return 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/desktop-fx/com/jme3/water/WaterFilter.java b/engine/src/desktop-fx/com/jme3/water/WaterFilter.java new file mode 100644 index 000000000..0d9010c80 --- /dev/null +++ b/engine/src/desktop-fx/com/jme3/water/WaterFilter.java @@ -0,0 +1,798 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Plane; +import com.jme3.math.Ray; +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.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +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 java.io.IOException; + +/** + * + * @author nehon + */ +public class WaterFilter extends Filter { + + private Pass reflectionPass; + protected Spatial reflectionScene; + protected ViewPort reflectionView; + private Texture2D normalTexture; + private Texture2D foamTexture; + private Texture2D heightTexture; + private Plane plane; + private Camera reflectionCam; + private float speed = 1; + protected Ray ray = new Ray(); + private Vector3f targetLocation = new Vector3f(); + private Vector3f lightDirection; + 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 float waterHeight = 0.0f; + private float waterTransparency = 0.1f; + private float normalScale = 3.0f; + private float refractionConstant = 0.5f; + private float maxAmplitude = 1.5f; + private ColorRGBA lightColor = ColorRGBA.White; + private float shoreHardness = 0.1f; + private float foamHardness = 1.0f; + private float refractionStrength = 0.0f; + private float waveScale = 0.005f; + private Vector3f foamExistence = new Vector3f(0.45f, 4.35f, 1.5f); + private Vector3f colorExtinction = new Vector3f(5.0f, 20.0f, 30.0f); + private float sunScale = 3.0f; + private float shininess = 0.7f; + private ColorRGBA waterColor = new ColorRGBA(0.0078f, 0.5176f, 0.5f, 1.0f); + private ColorRGBA deepWaterColor = new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f); + private Vector2f windDirection = new Vector2f(0.0f, -1.0f); + private int reflectionMapSize = 512; + private boolean useRipples = true; + private boolean useHQShoreline = true; + private boolean useSpecular = true; + private boolean useFoam = true; + private boolean useRefraction = true; + private float time = 0; + private float savedTpf = 0; + private float reflectionDisplace = 30; + private float foamIntensity = 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 + public boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected Format getDefaultPassDepthFormat() { + return Format.Depth; + } + + @Override + public void preFrame(float tpf) { + time = time + (tpf * speed); + material.setFloat("Time", time); + savedTpf = tpf; + } + + @Override + public void preRender(RenderManager renderManager, ViewPort viewPort) { + 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()); + reflectionCam.lookAt(targetLocation, Vector3f.UNIT_Y); + if (inv) { + reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal()); + } + + renderManager.renderViewPort(reflectionView, savedTpf); + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + renderManager.setCamera(sceneCam, false); + } + + @Override + public Material getMaterial() { + return material; + } + + @Override + public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + + reflectionPass = new Pass(); + reflectionPass.init(renderManager.getRenderer(), reflectionMapSize, reflectionMapSize, Format.RGBA8, Format.Depth); + reflectionCam = new Camera(reflectionMapSize, reflectionMapSize); + reflectionView = new ViewPort("reflectionView", reflectionCam); + reflectionView.setClearEnabled(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/gradient_map.jpg"); + if (foamTexture == null) { + foamTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg"); + } + heightTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/heightmap.jpg"); + + normalTexture.setWrap(WrapMode.Repeat); + foamTexture.setWrap(WrapMode.Repeat); + heightTexture.setWrap(WrapMode.Repeat); + + material = new Material(manager, "Common/MatDefs/Water/Water.j3md"); + material.setTexture("HeightMap", heightTexture); + 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("UseRefraction", useRefraction); + material.setFloat("m_ReflectionDisplace", reflectionDisplace); + material.setFloat("m_FoamIntensity", foamIntensity); + + } + + @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); + } + + /** + * 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; + } + + 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); + } + } + + 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); + } + } + + 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); + } + } + + /** + * retunrs 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 waterColour + */ + 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; + } + + @Override + public void cleanUpFilter(Renderer r) { + if (reflectionPass != null) { + reflectionPass.cleanup(r); + } + } + + /** + * 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); + } + + } + + /** + * + * @return + */ + public boolean isUseHQShoreline() { + return useHQShoreline; + } + + public void setUseHQShoreline(boolean useHQShoreline) { + this.useHQShoreline = useHQShoreline; + if (material != null) { + material.setBoolean("UseHQShoreline", useHQShoreline); + } + + } + + public boolean isUseRefraction() { + return useRefraction; + } + + public void setUseRefraction(boolean useRefraction) { + this.useRefraction = useRefraction; + if (material != null) { + material.setBoolean("UseRefraction", useRefraction); + } + + } + + public boolean isUseRipples() { + return useRipples; + } + + public void setUseRipples(boolean useRipples) { + this.useRipples = useRipples; + if (material != null) { + material.setBoolean("UseRipples", useRipples); + } + + } + + public boolean isUseSpecular() { + return useSpecular; + } + + public void setUseSpecular(boolean useSpecular) { + this.useSpecular = useSpecular; + if (material != null) { + material.setBoolean("UseSpecular", useSpecular); + } + } + + public float getFoamIntensity() { + return foamIntensity; + } + + public void setFoamIntensity(float foamIntensity) { + this.foamIntensity = foamIntensity; + if (material != null) { + material.setFloat("m_FoamIntensity", foamIntensity); + + } + } + + public float getReflectionDisplace() { + return reflectionDisplace; + } + + public void setReflectionDisplace(float reflectionDisplace) { + this.reflectionDisplace = reflectionDisplace; + if (material != null) { + material.setFloat("m_ReflectionDisplace", reflectionDisplace); + } + + + } +} 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 000000000..c595647da --- /dev/null +++ b/engine/src/desktop/com/jme3/app/AppletHarness.java @@ -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. + */ + +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.SwingUtilities; + +/** + * @author Kirill + */ +public class AppletHarness extends Applet { + + public static final HashMap appToApplet + = new HashMap(); + + 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){ + try { + InputStream in = appCfg.openStream(); + settings.load(in); + in.close(); + } catch (IOException ex){ + ex.printStackTrace(); + } + } + + if (assetCfg != null){ + settings.putString("AssetConfigURL", assetCfg.toString()); + } + + settings.setWidth(getWidth()); + settings.setHeight(getHeight()); + + JmeSystem.setLowPermissions(true); + + try{ + Class clazz = (Class) 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 000000000..e1c8c3d8b Binary files /dev/null and b/engine/src/desktop/com/jme3/app/Monkey.png 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 000000000..1a51d7c7c --- /dev/null +++ b/engine/src/desktop/com/jme3/app/SettingsDialog.java @@ -0,0 +1,697 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.*; +import java.awt.BorderLayout; +import java.awt.DisplayMode; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +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.DefaultComboBoxModel; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.UIManager; + +/** + * PropertiesDialog provides an interface to make use of the + * GameSettings class. The GameSettings 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 PropertiesDialog. Creates a + * properties dialog initialized for the primary display. + * + * @param source + * the AppSettings object to use for working with + * the properties file. + * @param imageFile + * the image file to use as the title of the dialog; + * null will result in to image being displayed + * @throws NullPointerException + * if the source is null + */ + public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) { + this(source, getURL(imageFile), loadSettings); + } + + /** + * Constructor for the PropertiesDialog. Creates a + * properties dialog initialized for the primary display. + * + * @param source + * the GameSettings object to use for working with + * the properties file. + * @param imageFile + * the image file to use as the title of the dialog; + * null will result in to image being displayed + * @param mainThreadTasks + * @throws JmeException + * if the source is null + */ + 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); + } + + /** + * setImage sets the background image of the dialog. + * + * @param image + * String 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) { + } + } + + /** + * setImage sets the background image of this dialog. + * + * @param image + * URL 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 + } + + /** + * showDialog sets this dialog as visble, and brings it to + * the front. + */ + public void showDialog() { + setLocationRelativeTo(null); + setVisible(true); + toFront(); + } + + /** + * init 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(Arrays.asList(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 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; + } + } + + /** + * verifyAndSaveCurrentSelection 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; + } + + /** + * setUpChooser retrieves all available display modes and + * places them in a JComboBox. 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; + } + + /** + * setUpRendererChooser sets the list of available renderers. + * Data is obtained from the DisplaySystem 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; + } + + /** + * updateDisplayChoices 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); + } + + /** + * updateResolutionChoices 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 updateDisplayChoices (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 DisplayModes. + */ + private static String[] getResolutions(DisplayMode[] modes) { + ArrayList resolutions = new ArrayList(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 depths = new ArrayList(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 freqs = new ArrayList(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 DisplayModes. Sorts by + * resolution, then bit depth, and then finally refresh rate. + */ + private class DisplayModeSorter implements Comparator { + + /** + * @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/ScreenshotAppState.java b/engine/src/desktop/com/jme3/app/state/ScreenshotAppState.java new file mode 100644 index 000000000..d7ddfc300 --- /dev/null +++ b/engine/src/desktop/com/jme3/app/state/ScreenshotAppState.java @@ -0,0 +1,92 @@ +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) { + super.initialize(stateManager, app); + + InputManager inputManager = app.getInputManager(); + inputManager.addMapping("ScreenShot", new KeyTrigger(KeyInput.KEY_SYSRQ)); + inputManager.addListener(this, "ScreenShot"); + + List vps = app.getRenderManager().getPostViews(); + ViewPort last = vps.get(vps.size()-1); + last.addProcessor(this); + + appName = app.getClass().getSimpleName(); + } + + 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/asset/AssetCache.java b/engine/src/desktop/com/jme3/asset/AssetCache.java new file mode 100644 index 000000000..b9dbfdb36 --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/AssetCache.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.asset; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.WeakHashMap; + +/** + * An AssetCache 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 smartKey; + public Asset asset; + } + + private final WeakHashMap smartCache + = new WeakHashMap(); + private final HashMap regularCache = new HashMap(); + + /** + * Adds a resource to the cache. + *

+ * Thread-safe. + * @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(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. + *

+ * Thread-safe. + */ + public boolean deleteFromCache(AssetKey key){ + if (key.useSmartCache()){ + throw new UnsupportedOperationException("You cannot delete from the smart cache"); + } + + synchronized (regularCache){ + return regularCache.remove(key) != null; + } + } + + /** + * Gets an object from the cache given an asset key. + *

+ * Thread-safe. + * @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(); + } + } +} diff --git a/engine/src/desktop/com/jme3/asset/Desktop.cfg b/engine/src/desktop/com/jme3/asset/Desktop.cfg new file mode 100644 index 000000000..55bacc779 --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/Desktop.cfg @@ -0,0 +1,20 @@ +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.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.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib \ No newline at end of file diff --git a/engine/src/desktop/com/jme3/asset/DesktopAssetManager.java b/engine/src/desktop/com/jme3/asset/DesktopAssetManager.java new file mode 100644 index 000000000..f4ed1dd68 --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/DesktopAssetManager.java @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AudioKey; +import com.jme3.audio.AudioData; +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.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AssetManager 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 final ThreadingManager threadingMan = new ThreadingManager(this); +// private final Set alreadyLoadingSet = new HashSet(); + + 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 setAssetEventListener(AssetEventListener listener){ + eventListener = listener; + } + + public void registerLoader(Class 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 clazz = null; + try{ + clazz = (Class) 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 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 clazz = null; + try{ + clazz = (Class) 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 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. + *

+ * Thread-safe. + */ + public boolean deleteFromCache(AssetKey key){ + return cache.deleteFromCache(key); + } + + /** + * Adds a resource to the cache. + *

+ * Thread-safe. + */ + 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; + } + + /** + * This method is thread-safe. + * @param name + * @return + * + * Thread-safe. + */ + public T loadAsset(AssetKey key){ + 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){ + logger.log(Level.WARNING,"No loader registered for type {0}.", + key.getExtension()); + return null; + } + + 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 null; + } + + try { + o = loader.load(info); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to load resource: "+key, ex); + } + if (o == null){ + logger.log(Level.WARNING, "Error occured while loading resource {0} using {1}", + new Object[]{key, loader.getClass().getSimpleName()}); + + return null; + }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 AssetKey(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/desktop/com/jme3/asset/ImplHandler.java b/engine/src/desktop/com/jme3/asset/ImplHandler.java new file mode 100644 index 000000000..366ff3324 --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/ImplHandler.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.asset; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * ImplHandler 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 ArrayList genericLocators = + new ArrayList(); + + private final HashMap loaders = + new HashMap(); + + 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(), "", 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(), "", ex); + } + return null; + } + } + + /** + * Attempts to locate the given resource name. + * @param name 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.size() == 0) + 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 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/desktop/com/jme3/asset/ThreadingManager.java b/engine/src/desktop/com/jme3/asset/ThreadingManager.java new file mode 100644 index 000000000..6ed2c742e --- /dev/null +++ b/engine/src/desktop/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; + +/** + * ThreadingManager 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 { + 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 { +// private final String[] resourceNames; +// public MultiLoadingTask(String[] resourceNames){ +// this.resourceNames = resourceNames; +// } +// public Void call(){ +// owner.loadContents(resourceNames); +// return null; +// } +// } + +// public Future loadContents(String ... names){ +// return executor.submit(new MultiLoadingTask(names)); +// } + +// public Future loadContent(String name) { +// return executor.submit(new LoadingTask(name)); +// } + + public static boolean isLoadingThread() { + return Thread.currentThread().getName().startsWith("pool"); + } + + +} diff --git a/engine/src/desktop/com/jme3/asset/plugins/ClasspathLocator.java b/engine/src/desktop/com/jme3/asset/plugins/ClasspathLocator.java new file mode 100644 index 000000000..03970cd75 --- /dev/null +++ b/engine/src/desktop/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.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.logging.Logger; + +/** + * The ClasspathLocator 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 = ""; + + private static class ClasspathAssetInfo extends AssetInfo { + + private URLConnection conn; + + public ClasspathAssetInfo(AssetManager manager, AssetKey key, URLConnection conn){ + super(manager, key); + this.conn = conn; + } + + @Override + public InputStream openStream() { + try{ + return conn.getInputStream(); + }catch (IOException ex){ + return null; // failure.. + } + } + } + + 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; + + try{ + URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + return new ClasspathAssetInfo(manager, key, conn); + }catch (IOException ex){ + return null; + } + + } + + +} diff --git a/engine/src/desktop/com/jme3/asset/plugins/FileLocator.java b/engine/src/desktop/com/jme3/asset/plugins/FileLocator.java new file mode 100644 index 000000000..3f7a0938a --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/plugins/FileLocator.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.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.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +/** + * FileLocator 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(); + + root = new File(rootPath); + if (!root.isDirectory()) + throw new RuntimeException("Given root path not a directory"); + } + + 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){ + return null; + } + } + } + + public AssetInfo locate(AssetManager manager, AssetKey key) { + String name = key.getName(); + File file = new File(root, name); + if (file.exists() && file.isFile()){ + return new AssetInfoFile(manager, key, file); + }else{ + return null; + } + } + +} diff --git a/engine/src/desktop/com/jme3/asset/plugins/HttpZipLocator.java b/engine/src/desktop/com/jme3/asset/plugins/HttpZipLocator.java new file mode 100644 index 000000000..b7293b9c6 --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/plugins/HttpZipLocator.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.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 entries; + + 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) { + // First, count the number of characters in the sequence + int count = 0; + int max = off + len; + int i = off; + while (i < max) { + int c = b[i++] & 0xff; + switch (c >> 4) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + // 0xxxxxxx + count++; + break; + case 12: case 13: + // 110xxxxx 10xxxxxx + if ((int)(b[i++] & 0xc0) != 0x80) { + throw new IllegalArgumentException(); + } + count++; + break; + case 14: + // 1110xxxx 10xxxxxx 10xxxxxx + if (((int)(b[i++] & 0xc0) != 0x80) || + ((int)(b[i++] & 0xc0) != 0x80)) { + throw new IllegalArgumentException(); + } + count++; + break; + default: + // 10xxxxxx, 1111xxxx + throw new IllegalArgumentException(); + } + } + if (i != max) { + throw new IllegalArgumentException(); + } + // Now decode the characters... + char[] cs = new char[count]; + i = 0; + while (off < max) { + int c = b[off++] & 0xff; + switch (c >> 4) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + // 0xxxxxxx + cs[i++] = (char)c; + break; + case 12: case 13: + // 110xxxxx 10xxxxxx + cs[i++] = (char)(((c & 0x1f) << 6) | (b[off++] & 0x3f)); + break; + case 14: + // 1110xxxx 10xxxxxx 10xxxxxx + int t = (b[off++] & 0x3f) << 6; + cs[i++] = (char)(((c & 0x0f) << 12) | t | (b[off++] & 0x3f)); + break; + default: + // 10xxxxxx, 1111xxxx + throw new IllegalArgumentException(); + } + } + return new String(cs, 0, count); + } + + 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(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/desktop/com/jme3/asset/plugins/UrlLocator.java b/engine/src/desktop/com/jme3/asset/plugins/UrlLocator.java new file mode 100644 index 000000000..3bf465f25 --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/plugins/UrlLocator.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.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * UrlLocator 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 Kiirill Vainer + */ +public class UrlLocator implements AssetLocator { + + private static final Logger logger = Logger.getLogger(UrlLocator.class.getName()); + private URL root; + + private static class UrlAssetInfo extends AssetInfo { + + private InputStream in; + + public UrlAssetInfo(AssetManager manager, AssetKey key, InputStream in){ + super(manager, key); + this.in = in; + } + + @Override + public InputStream openStream() { + return in; + } + } + + 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{ + URL url = new URL(root, name); + URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + conn.setDoOutput(false); + InputStream in; + try { + in = conn.getInputStream(); + if (in == null) + return null; + } catch (FileNotFoundException ex){ + return null; + } + + return new UrlAssetInfo(manager, key, in); + }catch (IOException ex){ + logger.log(Level.WARNING, "Error while locating " + name, ex); + return null; + } + } + + +} diff --git a/engine/src/desktop/com/jme3/asset/plugins/ZipLocator.java b/engine/src/desktop/com/jme3/asset/plugins/ZipLocator.java new file mode 100644 index 000000000..4638dd41c --- /dev/null +++ b/engine/src/desktop/com/jme3/asset/plugins/ZipLocator.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.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.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * ZipLocator 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){ + logger.log(Level.WARNING, "Failed to load zip entry: "+entry, ex); + } + return null; + } + } + + public void setRootPath(String rootPath) { + try{ + zipfile = new ZipFile(new File(rootPath), ZipFile.OPEN_READ); + }catch (IOException ex){ + logger.log(Level.WARNING, "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/desktop/com/jme3/input/awt/AwtKeyInput.java b/engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java new file mode 100644 index 000000000..d3705b245 --- /dev/null +++ b/engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java @@ -0,0 +1,598 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +/** + * AwtKeyInput + * + * @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 RawInputListener listener; + private boolean inited = false; + private List eventQueue = new LinkedList(); + private Component component; + + public AwtKeyInput(Component comp){ + this.component = comp; + } + + public void initialize() { + inited = true; + component.addKeyListener(this); + + logger.info("Key input initialized."); + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public int getKeyCount() { + return KeyEvent.KEY_LAST+1; + } + + public void update() { + // flush events to listener + for (int i = 0; i < eventQueue.size(); i++){ + listener.onKeyEvent(eventQueue.get(i)); + } + eventQueue.clear(); + } + + public void destroy() { + inited = false; + + component.removeKeyListener(this); + logger.info("Key input destroyed."); + } + + public boolean isInitialized() { + return inited; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public void keyTyped(KeyEvent evt) { + // key code is zero for typed events + int code = 0; + //int code = convertAwtKey(evt.getKeyCode()); + KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, true); + keyEvent.setTime(evt.getWhen()); + 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()); + 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()); + eventQueue.add(keyEvent); + } + + /** + * convertJmeCode 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; + } + + /** + * convertAwtKey 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 000000000..6b687cf84 --- /dev/null +++ b/engine/src/desktop/com/jme3/input/awt/AwtMouseInput.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 de.lessvoid.nifty.input.mouse.MouseInputEvent; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Point; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; + +/** + * AwtMouseInput + * + * @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 inited = false; + + private boolean visible = true; + + private RawInputListener listener; + + private Component component; + + private ArrayList eventQueue = new ArrayList(); + 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 int eventsSinceRecenter; + + public AwtMouseInput(Component comp) { + this.component = comp; + 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 initialize() { + inited = true; + component.addMouseListener(this); + component.addMouseMotionListener(this); + component.addMouseWheelListener(this); + + logger.info("Mouse input initialized."); + } + + public void destroy() { + inited = false; + robot = null; + + component.removeMouseListener(this); + component.removeMouseMotionListener(this); + component.removeMouseWheelListener(this); + logger.info("Mouse input destroyed."); + } + + public boolean isInitialized() { + return inited; + } + + public void setInputListener(RawInputListener listener){ + this.listener = listener; + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void setCursorVisible(boolean visible){ + if (this.visible != visible){ + component.setCursor(visible ? null : getTransparentCursor()); + this.visible = visible; + if (!visible) + recenterMouse(component); + } + } + + public void update() { + int newX = location.x; + int newY = location.y; + int newWheel = wheelPos; + + // invert DY + MouseMotionEvent evt = new MouseMotionEvent(newX, newY, + newX - lastEventX, + lastEventY - newY, + wheelPos, lastEventWheel - wheelPos); + listener.onMouseMotionEvent(evt); + + int size = eventQueue.size(); + for (int i = 0; i < size; i++){ + listener.onMouseButtonEvent(eventQueue.get(i)); + } + eventQueue.clear(); + + lastEventX = newX; + lastEventY = newY; + lastEventWheel = newWheel; + } + + private final 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 boolean isCursorVisible() { +// return( isCursorVisible ); +// } +// 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()); + eventQueue.add(evt); + } + + public void mouseReleased(MouseEvent arg0) { + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false, arg0.getX(), arg0.getY()); + evt.setTime(arg0.getWhen()); + 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; + } + + 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(); +// MHenze (cylab) Fix Issue 35: all accesses to the swingEvents list have to be synchronized for StandardGame to work... + // TODO MHenze (cylab): Cleanup. this members seem obsolete by the newly introduced above. +// absPoint.setLocation(location); +// if (lastPoint.x == Integer.MIN_VALUE) { +// lastPoint.setLocation(absPoint.x, absPoint.y); +// } +// lastPoint.setLocation(arg0.getPoint()); +// currentDeltaPoint.x = absPoint.x - lastPoint.x; +// currentDeltaPoint.y = -(absPoint.y - lastPoint.y); +// lastPoint.setLocation(location); + } + } + + // 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; +// SwingUtilities.invokeLater(new Runnable() { +// public void run() { + 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 = 0; + break; + case MouseEvent.BUTTON2: //middle + index = 2; + break; + case MouseEvent.BUTTON3: //right + index = 1; + 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 000000000..1d250b5af --- /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/JmeSystem.java b/engine/src/desktop/com/jme3/system/JmeSystem.java new file mode 100644 index 000000000..55a19229a --- /dev/null +++ b/engine/src/desktop/com/jme3/system/JmeSystem.java @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.DesktopAssetManager; +import com.jme3.audio.AudioRenderer; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; + +public class JmeSystem { + + public static 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, + + /** + * Google Android Smartphone OS + */ + Android + } + + private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); + + private static boolean initialized = false; + private static boolean lowPermissions = false; + + public static boolean trackDirectMemory(){ + return false; + } + + public static void setLowPermissions(boolean lowPerm){ + lowPermissions = lowPerm; + } + + public static boolean isLowPermissions() { + return lowPermissions; + } + + public static AssetManager newAssetManager(URL configFile){ + return new DesktopAssetManager(configFile); + } + + public static AssetManager newAssetManager(){ + return new DesktopAssetManager(null); + } + + public static 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); + final URL iconUrl = JmeSystem.class.getResource(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 static 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 + throw new UnsupportedOperationException("Unsupported architecture: "+arch); + } + + public static 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")){ + 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."); + } + } + + private static JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type){ + try{ + Class ctxClazz = null; + switch (type){ + case Canvas: + ctxClazz = (Class) Class.forName("com.jme3.system.lwjgl.LwjglCanvas"); + break; + case Display: + ctxClazz = (Class) Class.forName("com.jme3.system.lwjgl.LwjglDisplay"); + break; + case OffscreenSurface: + ctxClazz = (Class) 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 static JmeContext newContextJogl(AppSettings settings, JmeContext.Type type){ + try{ + Class ctxClazz = null; + switch (type){ + case Display: + ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglDisplay"); + break; + case Canvas: + ctxClazz = (Class) 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 static JmeContext newContextCustom(AppSettings settings, JmeContext.Type type){ + try{ + String className = settings.getRenderer().substring("CUSTOM".length()); + + Class ctxClazz = null; + ctxClazz = (Class) 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; + } + + public static JmeContext newContext(AppSettings settings, JmeContext.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; + } + + public static AudioRenderer newAudioRenderer(AppSettings settings){ + initialize(settings); + Class clazz = null; + try { + if (settings.getAudioRenderer().startsWith("LWJGL")){ + clazz = (Class) Class.forName("com.jme3.audio.lwjgl.LwjglAudioRenderer"); + }else if (settings.getAudioRenderer().startsWith("JOAL")){ + clazz = (Class) Class.forName("com.jme3.audio.joal.JoalAudioRenderer"); + }else{ + throw new UnsupportedOperationException( + "Unrecognizable audio renderer specified: "+ + settings.getAudioRenderer()); + } + + AudioRenderer ar = clazz.newInstance(); +// ar = new QueuedAudioRenderer(ar); + 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; + } + + public static 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); +// +// Logger.getLogger("com.jme3").setLevel(Level.FINEST); +// } +// } 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); + } + } + } + + public static String getFullName(){ + return "jMonkey Engine 3 Alpha 0.6"; + } + + public static InputStream getResourceAsStream(String name){ + return JmeSystem.class.getResourceAsStream(name); + } + + public static URL getResource(String name){ + return JmeSystem.class.getResource(name); + } +} 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 000000000..49a7a90c3 --- /dev/null +++ b/engine/src/desktop/com/jme3/system/Natives.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.system.JmeSystem.Platform; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +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 class Natives { + + private static final Logger logger = Logger.getLogger(Natives.class.getName()); + private static final byte[] buf = new byte[1024]; + private static File workingDir = new File("").getAbsoluteFile(); + + public static void setExtractionDir(String name){ + workingDir = new File(name).getAbsoluteFile(); + } + + protected static void extractNativeLib(String sysName, String name) throws IOException{ + String fullname = System.mapLibraryName(name); + + String path = "native/"+sysName+"/" + fullname; + InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + //InputStream in = Natives.class.getResourceAsStream(); + if (in == null) { + logger.log(Level.WARNING, "Cannot locate native library: {0}/{1}", + new String[]{ sysName, fullname} ); + return; + } + File targetFile = new File(workingDir, fullname); + try { + OutputStream out = new FileOutputStream(targetFile); + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } catch (FileNotFoundException ex){ + if (ex.getMessage().contains("used by another process")) + return; + + throw ex; + } + + logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile}); + } + + private static String getExtractionDir(){ + URL temp = Natives.class.getResource(""); + if (temp != null) { + StringBuilder sb = new StringBuilder(temp.toString()); + if (sb.indexOf("jar:") == 0) { + sb.delete(0, 4); + sb.delete(sb.indexOf("!"), sb.length()); + sb.delete(sb.lastIndexOf("/") + 1, sb.length()); + } + try { + return new URL(sb.toString()).toString(); + } catch (MalformedURLException ex) { + return null; + } + } + return null; + } + + protected static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException{ + String renderer = settings.getRenderer(); + String audioRenderer = settings.getAudioRenderer(); + boolean needLWJGL = false; + boolean needGG = false; + boolean needJOGL = false; + boolean needJOAL = false; + boolean needOAL = false; + boolean needJInput = false; + if (renderer != null){ + if (renderer.startsWith("LWJGL")){ + needLWJGL = true; + }else if (renderer.startsWith("JOGL")){ + needGG = true; + needJOGL = true; + } + } + if (audioRenderer != null){ + if (audioRenderer.equals("LWJGL")){ + needLWJGL = true; + needOAL = true; + }else if (audioRenderer.equals("JOAL")){ + needJOAL = true; + needGG = true; + needOAL = true; + } + } + needJInput = settings.useJoysticks(); + + if (needLWJGL){ + logger.log(Level.INFO, "Extraction Directory #1: {0}", getExtractionDir()); + logger.log(Level.INFO, "Extraction Directory #2: {0}", workingDir.toString()); + logger.log(Level.INFO, "Extraction Directory #3: {0}", System.getProperty("user.dir")); + // LWJGL supports this feature where + // it can load libraries from this path. + // This is a fallback method in case the OS doesn't load + // native libraries from the working directory (e.g Linux). + System.setProperty("org.lwjgl.librarypath", workingDir.toString()); + } + + switch (platform){ + case Windows64: + if (needLWJGL){ + extractNativeLib("windows", "lwjgl64"); + } + if (needJOGL){ + extractNativeLib("win64", "jogl_awt"); + extractNativeLib("win64", "jogl"); + } + + if (needJOAL) + extractNativeLib("win64", "joal_native"); + + if (needGG) + extractNativeLib("win64", "gluegen-rt"); + + if (needOAL) + extractNativeLib("windows", "OpenAL64"); + + if (needJInput){ + extractNativeLib("windows", "jinput-dx8_64"); + extractNativeLib("windows", "jinput-raw_64"); + } + break; + case Windows32: + if (needLWJGL){ + extractNativeLib("windows", "lwjgl"); + } + if (needJOGL){ + extractNativeLib("win32", "jogl_awt"); + extractNativeLib("win32", "jogl"); + } + + if (needJOAL) + extractNativeLib("win32", "joal_native"); + + if (needGG) + extractNativeLib("win32", "gluegen-rt"); + + if (needOAL) + extractNativeLib("windows", "OpenAL32"); + + if (needJInput){ + extractNativeLib("windows", "jinput-dx8"); + extractNativeLib("windows", "jinput-raw"); + } + break; + case Linux64: + if (needLWJGL){ + extractNativeLib("linux", "lwjgl64"); + } + if (needJOGL){ + extractNativeLib("linux64", "jogl_awt"); + extractNativeLib("linux64", "jogl"); + } + + if (needJOAL) + extractNativeLib("linux64", "joal_native"); + + if (needGG) + extractNativeLib("linux64", "gluegen-rt"); + + if (needJInput) + extractNativeLib("linux", "jinput-linux64"); + + if (needOAL) + extractNativeLib("linux", "openal64"); + + break; + case Linux32: + if (needLWJGL){ + extractNativeLib("linux", "lwjgl"); + } + if (needJOGL){ + extractNativeLib("linux32", "jogl_awt"); + extractNativeLib("linux32", "jogl"); + } + + if (needJOAL) + extractNativeLib("linux32", "joal_native"); + + if (needGG) + extractNativeLib("linux32", "gluegen-rt"); + + if (needJInput) + extractNativeLib("linux", "jinput-linux"); + + if (needOAL) + extractNativeLib("linux", "openal"); + + break; + case MacOSX_PPC32: + if (needLWJGL){ + extractNativeLib("macosx", "lwjgl"); + } + if (needJOGL){ + extractNativeLib("macosx_ppc", "jogl_awt"); + extractNativeLib("macosx_ppc", "jogl"); + } + + if (needJOAL) + throw new UnsupportedOperationException("JOAL not available on Mac OS PPC"); + + if (needGG) + extractNativeLib("macosx_ppc", "gluegen-rt"); + +// if (needOAL) +// extractNativeLib("macosx", "openal"); + + if (needJInput) + extractNativeLib("macosx", "jinput-osx"); + + break; + case MacOSX32: + if (needLWJGL){ + extractNativeLib("macosx", "lwjgl"); + } + if (needJOGL){ + extractNativeLib("macosx_universal", "jogl_awt"); + extractNativeLib("macosx_universal", "jogl"); + } + + if (needJOAL) + extractNativeLib("macosx_universal", "joal_native"); + + if (needGG) + extractNativeLib("macosx_universal", "gluegen-rt"); + +// if (needOAL) +// extractNativeLib("macosx", "openal"); + + if (needJInput) + extractNativeLib("macosx", "jinput-osx"); + + break; + case MacOSX_PPC64: + if (needLWJGL){ + extractNativeLib("macosx", "lwjgl"); + } + if (needJOGL){ + extractNativeLib("macosx_ppc", "jogl_awt"); + extractNativeLib("macosx_ppc", "jogl"); + } + + if (needJOAL) + throw new UnsupportedOperationException("JOAL not available on Mac OS 64 bit"); + + if (needGG) + extractNativeLib("macosx_ppc", "gluegen-rt"); + +// if (needOAL) +// extractNativeLib("macosx", "openal"); + + if (needJInput) + extractNativeLib("macosx", "jinput-osx"); + + break; + case MacOSX64: + if (needLWJGL){ + extractNativeLib("macosx", "lwjgl"); + } + if (needJOGL){ + extractNativeLib("macosx_universal", "jogl_awt"); + extractNativeLib("macosx_universal", "jogl"); + } + + if (needJOAL) + throw new UnsupportedOperationException("JOAL not available on Mac OS 64 bit"); + + if (needGG) + extractNativeLib("macosx_universal", "gluegen-rt"); + +// if (needOAL) +// extractNativeLib("macosx", "openal"); + + if (needJInput) + extractNativeLib("macosx", "jinput-osx"); + + break; + + + } + } + +} 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 000000000..0db07f06d --- /dev/null +++ b/engine/src/desktop/com/jme3/texture/plugins/AWTLoader.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.*; +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.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DirectColorModel; +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 byte[] extractImageData(BufferedImage img){ + DataBuffer buf = img.getRaster().getDataBuffer(); + switch (buf.getDataType()){ + case DataBuffer.TYPE_BYTE: + DataBufferByte byteBuf = (DataBufferByte) buf; + return byteBuf.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); + } + } + + public Image load(BufferedImage img, boolean flipY){ + int width = img.getWidth(); + int height = img.getHeight(); + + switch (img.getType()){ + case BufferedImage.TYPE_3BYTE_BGR: // most common in JPEG images + byte[] dataBuf = extractImageData(img); + if (flipY) + flipImage(dataBuf, width, height, 24); + ByteBuffer data = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*3); + data.put(dataBuf); + return new Image(Format.BGR8, width, height, data); + case BufferedImage.TYPE_BYTE_GRAY: // grayscale fonts + byte[] dataBuf2 = extractImageData(img); + if (flipY) + flipImage(dataBuf2, width, height, 8); + ByteBuffer data2 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()); + data2.put(dataBuf2); + return new Image(Format.Luminance8, width, height, data2); + 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){ + InputStream in = info.openStream(); + boolean flip = ((TextureKey) info.getKey()).isFlipY(); + Image img = load(in, flip); + in.close(); + return img; + } + return null; + } +} diff --git a/engine/src/games/jme3game/cubefield/CubeField.java b/engine/src/games/jme3game/cubefield/CubeField.java new file mode 100644 index 000000000..d9f57e2a2 --- /dev/null +++ b/engine/src/games/jme3game/cubefield/CubeField.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3game.cubefield; + +import java.util.ArrayList; + +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.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 cubeField; + private ArrayList obstacleColors; + private float speed, coreTime,coreTime2; + private float camAngle = 0; + private BitmapText fpsScoreText, pressStart; + private String boxSolid; + + 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); + statsView.setCullHint(CullHint.Always); + + 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(); + obstacleColors = new ArrayList(); + + 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); + boxSolid = "Common/MatDefs/Misc/SolidColor.j3md"; + 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, boxSolid); + 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/SolidColor.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/SolidColor.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/SolidColor.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(); + boxSolid(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); + boxSolid(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); + boxSolid(false); + playerMaterial.setColor("Color", ColorRGBA.White); + break; + case 6: + obstacleColors.set(0, ColorRGBA.White); + boxSolid(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); + } + + /** + * Changes the boolean variable boxSolid + * @param solid the boolean to determine if the boxes will be solid or wireFrame + */ + private void boxSolid(boolean solid) { + if (solid == false){ + boxSolid = "Common/MatDefs/Misc/WireColor.j3md"; + }else{ + boxSolid = "Common/MatDefs/Misc/SolidColor.j3md"; + } + } + +} \ No newline at end of file diff --git a/engine/src/games/jme3game/golem/Golem.java b/engine/src/games/jme3game/golem/Golem.java new file mode 100644 index 000000000..c027c0750 --- /dev/null +++ b/engine/src/games/jme3game/golem/Golem.java @@ -0,0 +1,551 @@ +/** + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3game.golem; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimEventListener; +import com.jme3.animation.LoopMode; +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.nodes.PhysicsCharacterNode; +import com.jme3.bullet.nodes.PhysicsNode; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.effect.EmitterSphereShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +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.Vector2f; +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.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.system.AppSettings; +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; +import jme3tools.converters.ImageToAwt; + +/** + * + * @author normenhansen + */ +public class Golem extends SimpleApplication + implements ActionListener, PhysicsCollisionListener, AnimEventListener { + + // constants + static final Quaternion ROTATE_LEFT = new Quaternion(); + + static { + ROTATE_LEFT.fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y); + } + // + float myTimer; + private BulletAppState bulletAppState; + //character + PhysicsCharacterNode characterNode; + Node character; + //temp vectors + Vector3f walkDirection = new Vector3f(); + Quaternion modelRotation = new Quaternion(); + Vector3f modelDirection = new Vector3f(); + Vector3f modelRight = new Vector3f(); + //terrain + TerrainQuad terrain; + Node terrainNode; + //Materials + Material matTerrain; + Material matWire; + Material matBullet; + Material matBomb; + Material matRock; + //animation + AnimChannel animationChannel; + AnimChannel shootingChannel; + AnimControl animationControl; + float airTime = 0; + //camera + boolean left = false, right = false, up = false, down = false; + ChaseCamera chaseCam; + //player's bullet + Sphere bullet; + SphereCollisionShape bulletCollisionShape; + //enemy's bomb + Sphere bomb; + SphereCollisionShape bombCollisionShape; + Vector3f wallLoc = new Vector3f(0, 11, -105); + Vector3f enemyLoc = new Vector3f(wallLoc.x + 15f, wallLoc.y + 15, wallLoc.z); + Vector3f trajectory = new Vector3f(0, 10, 10); + //explosion + ParticleEmitter effect; + //brick wall + private Node wallNode; + Box brick; + float bLength = 1.6f; + float bWidth = 1f; + float bHeight = 1f; + + public static void main(String[] args) { + Golem app = new Golem(); + AppSettings settings = new AppSettings(true); + settings.setSamples(4); + app.setSettings(settings); + app.setShowSettings(false); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + stateManager.attach(bulletAppState); + setupKeys(); + prepareBullet(); + prepareBomb(); + prepareEffect(); + createLight(); + createSky(); + createTerrain(); + createWall(wallLoc); + createCharacter(); + setupChaseCamera(); + setupAnimationController(); + + } + + private void createWall(Vector3f off) { + wallNode = new Node("the wall"); + + matRock = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + Texture tex_ml = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"); + matRock.setTexture("ColorMap", tex_ml); + + brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth); + brick.scaleTextureCoordinates(new Vector2f(1f, 1f)); + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 10; col++) { + Vector3f vt = new Vector3f( + off.x + (col + 1f) * bLength * 2.01f, + off.y + (row + 1f) * bHeight * 2.01f, + off.z); + addBrick(vt); + } + } + this.rootNode.attachChild(wallNode); + } + + private void addBrick(Vector3f ori) { + Geometry reBoxg = new Geometry("brick", brick); + reBoxg.setMaterial(matRock); + PhysicsNode brickNode = new PhysicsNode( + reBoxg, + new BoxCollisionShape(new Vector3f(bLength, bHeight, bWidth)), 1.5f); + brickNode.setLocalTranslation(ori); + brickNode.setShadowMode(ShadowMode.CastAndReceive); + wallNode.attachChild(brickNode); + this.getPhysicsSpace().add(brickNode); + } + + private void createCharacter() { + CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f); + characterNode = new PhysicsCharacterNode(capsule, 0.01f); + character = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + characterNode.attachChild(character); + characterNode.setName("the Golem"); + characterNode.setLocalTranslation(new Vector3f(0, 10, 50)); + characterNode.setMaxSlope(FastMath.PI / 2f * 3f); + rootNode.attachChild(characterNode); + getPhysicsSpace().add(characterNode); + } + + 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/WireColor.j3md"); + matBullet.setColor("Color", ColorRGBA.Green); + getPhysicsSpace().addCollisionListener(this); + } + + private void prepareBomb() { + bomb = new Sphere(32, 32, 2f, true, false); + bomb.setTextureMode(TextureMode.Projected); + bombCollisionShape = new SphereCollisionShape(2); + matBomb = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + matBomb.setColor("Color", ColorRGBA.Red); + 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(-5f); + 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); + effect.setCullHint(CullHint.Never); + 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); + +// PointLight spotlight = new PointLight(); +// spotlight.setPosition(Vector3f.ZERO); +// spotlight.setRadius(50); +// spotlight.setColor(ColorRGBA.White); +// rootNode.addLight(spotlight); + } + + private void createSky() { + rootNode.attachChild( + SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + } + + private void createTerrain() { + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md"); + matTerrain.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap2.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/fortress512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("Tex1", grass); + matTerrain.setFloat("Tex1Scale", 64f); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("Tex2", dirt); + matTerrain.setFloat("Tex2Scale", 32f); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("Tex3", rock); + matTerrain.setFloat("Tex3Scale", 128f); + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap( + ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + terrain.setLocalScale(new Vector3f(1, 1, 1)); + + terrainNode = new PhysicsNode(CollisionShapeFactory.createMeshShape(terrain),0); + terrainNode.attachChild(terrain); + rootNode.attachChild(terrainNode); + getPhysicsSpace().add(terrainNode); + } + + private void setupChaseCamera() { + flyCam.setEnabled(false); + chaseCam = new ChaseCamera(cam, characterNode, inputManager); + } + + private void setupAnimationController() { + animationControl = character.getControl(AnimControl.class); + animationControl.addListener(this); + animationChannel = animationControl.createChannel(); + shootingChannel = animationControl.createChannel(); +// System.out.println(animationControl.getSkeleton()); + 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) { + rootNode.updateLogicalState(tpf); + rootNode.updateGeometricState(); + Vector3f camDir = cam.getDirection().clone().multLocal(0.2f); + Vector3f camLeft = cam.getLeft().clone().multLocal(0.2f); + camDir.y = 0; + camLeft.y = 0; + walkDirection.set(0, 0, 0); + modelDirection.set(0, 0, 2); + if (left) { + walkDirection.addLocal(camLeft); + } + if (right) { + walkDirection.addLocal(camLeft.negate()); + } + if (up) { + walkDirection.addLocal(camDir); + } + if (down) { + walkDirection.addLocal(camDir.negate()); + } + if (!characterNode.onGround()) { + airTime = airTime + tpf; + } else { + airTime = 0; + } + if (walkDirection.length() == 0) { + if (!"stand".equals(animationChannel.getAnimationName())) { + animationChannel.setAnim("stand", 1f); + } + } else { + modelRotation.lookAt(walkDirection, Vector3f.UNIT_Y); + if (airTime > .3f) { + if (!"stand".equals(animationChannel.getAnimationName())) { + animationChannel.setAnim("stand"); + } + } else if (!"Walk".equals(animationChannel.getAnimationName())) { + animationChannel.setAnim("Walk", 0.7f); + } + } + character.setLocalRotation(modelRotation); + modelRotation.multLocal(modelDirection); + modelRight.set(modelDirection); + ROTATE_LEFT.multLocal(modelRight); + characterNode.setWalkDirection(walkDirection); + + // attack + myTimer += tpf; + if (myTimer > 10) { + shootBomb(); + myTimer = 0; + } + + + } + + 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("CharForw")) { + if (value) { + up = true; + } else { + up = false; + } + } else if (binding.equals("CharBack")) { + if (value) { + down = true; + } else { + down = false; + } + } else if (binding.equals("CharJump")) { + characterNode.jump(); + } else if (binding.equals("CharShoot") && !value) { + shootBullet(); + } + } + + private void shootBullet() { + shootingChannel.setAnim("Dodge", 0.1f); + shootingChannel.setLoopMode(LoopMode.DontLoop); + Geometry bulletGeo = new Geometry("bullet", bullet); + bulletGeo.setMaterial(matBullet); + PhysicsNode bulletNode = new PhysicsNode(bulletGeo, bulletCollisionShape, 1); + bulletNode.setCcdMotionThreshold(0.1f); + bulletNode.setName("bullet"); + bulletNode.setLocalTranslation(characterNode.getLocalTranslation().add(modelDirection.mult(1.8f).addLocal(modelRight.mult(0.9f)))); + bulletNode.setShadowMode(ShadowMode.CastAndReceive); + bulletNode.setLinearVelocity(modelDirection.mult(40)); + rootNode.attachChild(bulletNode); + getPhysicsSpace().add(bulletNode); + } + + private void shootBomb() { + Geometry bombGeo = new Geometry("bomb", bomb); + bombGeo.setMaterial(matBomb); + PhysicsNode bombNode = new PhysicsNode(bombGeo, bombCollisionShape, 1); + bombNode.setCcdMotionThreshold(0.1f); + bombNode.setName("bomb"); + bombNode.setLocalTranslation(enemyLoc); + bombNode.setShadowMode(ShadowMode.Cast); + + Vector3f w = wallNode.getWorldTranslation(); + Vector3f c = characterNode.getWorldTranslation(); + bombNode.setLinearVelocity(w.subtract(c).normalize().mult(30)); // TODO + rootNode.attachChild(bombNode); + getPhysicsSpace().add(bombNode); + } + + public void collision(PhysicsCollisionEvent event) { + if ("bullet".equals(event.getNodeA().getName())) { + Spatial node = event.getNodeA(); + bulletHitSomething(node); + } else if ("bullet".equals(event.getNodeB().getName())) { + Spatial node = event.getNodeB(); + bulletHitSomething(node); + } + if ("bomb".equals(event.getNodeA().getName())) { + Spatial agent = event.getNodeA(); + Spatial target = event.getNodeB(); + bombHitSomething(agent, target); + } else if ("bomb".equals(event.getNodeB().getName())) { + Spatial agent = event.getNodeB(); + Spatial target = event.getNodeA(); + bombHitSomething(agent, target); + } + } + + private void bombHitSomething(Spatial a, Spatial t) { + getPhysicsSpace().remove(a); + a.removeFromParent(); + effect.killAllParticles(); + effect.setLocalTranslation(t.getLocalTranslation()); + effect.emitAllParticles(); + if ("The Golem".equals(t.getName())) { + System.out.println("*** A bomb hit " + t.getName() + "! ***"); + } + } + + private void bulletHitSomething(Spatial node) { + getPhysicsSpace().remove(node); + if (!node.getName().equals("terrain")) { + node.removeFromParent(); + } + 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) { + } + + private void AxisRods() { + Material mat = new Material(assetManager, + "Common/MatDefs/Misc/SolidColor.j3md"); + mat.setColor("Color", ColorRGBA.White); + Box x = new Box(Vector3f.ZERO, 100f, 0.5f, 0.5f); + Geometry gx = new Geometry("Box", x); + gx.setMaterial(mat); + rootNode.attachChild(gx); + Box y = new Box(Vector3f.ZERO, 0.5f, 100f, 0.5f); + Geometry gy = new Geometry("Box", y); + gy.setMaterial(mat); + rootNode.attachChild(gy); + Box z = new Box(Vector3f.ZERO, 0.1f, 0.1f, 100f); + Geometry gz = new Geometry("Box", z); + gz.setMaterial(mat); + rootNode.attachChild(gz); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + /** Mapping WASD keys for walking, return for jumping, space for shooting */ + private void setupKeys() { + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("CharForw", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("CharBack", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("CharJump", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "wireframe"); + inputManager.addListener(this, "CharLeft"); + inputManager.addListener(this, "CharRight"); + inputManager.addListener(this, "CharForw"); + inputManager.addListener(this, "CharBack"); + inputManager.addListener(this, "CharJump"); + inputManager.addListener(this, "CharShoot"); + } +} diff --git a/engine/src/jbullet/com/jme3/app/SimpleBulletApplication.java b/engine/src/jbullet/com/jme3/app/SimpleBulletApplication.java new file mode 100644 index 000000000..9b4e1b4a3 --- /dev/null +++ b/engine/src/jbullet/com/jme3/app/SimpleBulletApplication.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.app; + +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; + +/** + * + * @author normenhansen + * @deprecated in favor of using BulletAppState:
+ * stateManager.attach(new BulletAppState()); + */ +@Deprecated +public abstract class SimpleBulletApplication extends SimpleApplication implements PhysicsTickListener{ + BulletAppState bulletAppState; + + @Override + public void initialize() { + bulletAppState=new BulletAppState(); + bulletAppState.startPhysics(); + super.initialize(); + stateManager.attach(bulletAppState); + getPhysicsSpace().addTickListener(this); + } + + public void simplePhysicsUpdate(float tpf){ + + } + + public void physicsTick(PhysicsSpace space, float f) { + simplePhysicsUpdate(f); + } + + public PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } +} diff --git a/engine/src/jbullet/com/jme3/bullet/BulletAppState.java b/engine/src/jbullet/com/jme3/bullet/BulletAppState.java new file mode 100644 index 000000000..3617d15ea --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/BulletAppState.java @@ -0,0 +1,287 @@ +/* + * 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.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * BulletAppState 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 call = new Callable() { + + 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 parallelPhysicsUpdate = new Callable() { + + public Boolean call() throws Exception { + pSpace.update(tpf * getSpeed()); + return true; + } + }; + long detachedPhysicsLastUpdate = 0; + private Callable detachedPhysicsUpdate = new Callable() { + + 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 setActive(boolean active) { + this.active = active; + } + + public boolean isActive() { + 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.
+ * Multiple BulletAppStates will execute in parallel in this mode. + */ + PARALLEL, + /** + * Detached threaded mode; each physics space executes independently on another thread, + * only location and rotation is transferred thread safe, + * all other physics operations including adding and removing of objects to the physics space + * have to be done from the physics thread. (Creation of objects is safe on any thread except for vehicle) + * @deprecated since native bullet will be parallelized at the time physics is moved to native + */ + @Deprecated + DETACHED + } +} 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 000000000..40c4dbc83 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/PhysicsSpace.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; + +import com.bulletphysics.BulletGlobals; +import com.bulletphysics.ContactAddedCallback; +import com.bulletphysics.ContactDestroyedCallback; +import com.bulletphysics.ContactProcessedCallback; +import com.bulletphysics.collision.broadphase.AxisSweep3; +import com.bulletphysics.collision.broadphase.AxisSweep3_32; +import com.bulletphysics.collision.broadphase.BroadphaseInterface; +import com.bulletphysics.collision.broadphase.BroadphaseProxy; +import com.bulletphysics.collision.broadphase.CollisionFilterGroups; +import com.bulletphysics.collision.broadphase.DbvtBroadphase; +import com.bulletphysics.collision.broadphase.OverlapFilterCallback; +import com.bulletphysics.collision.broadphase.SimpleBroadphase; +import com.bulletphysics.collision.dispatch.CollisionDispatcher; +import com.bulletphysics.collision.dispatch.CollisionObject; +import com.bulletphysics.collision.dispatch.CollisionWorld; +import com.bulletphysics.collision.dispatch.CollisionWorld.LocalConvexResult; +import com.bulletphysics.collision.dispatch.CollisionWorld.LocalRayResult; +import com.bulletphysics.collision.dispatch.DefaultCollisionConfiguration; +import com.bulletphysics.collision.dispatch.GhostPairCallback; +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.math.Vector3f; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionEventFactory; +import com.jme3.bullet.collision.PhysicsCollisionGroupListener; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.PhysicsSweepTestResult; +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.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.math.Transform; +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; + +/** + *

PhysicsSpace - The central jbullet-jme physics space

+ * @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>> pQueueTL = + new ThreadLocal>>() { + + @Override + protected ConcurrentLinkedQueue> initialValue() { + return new ConcurrentLinkedQueue>(); + } + }; + private ConcurrentLinkedQueue> pQueue = new ConcurrentLinkedQueue>(); + private static ThreadLocal physicsSpaceTL = new ThreadLocal(); + private DiscreteDynamicsWorld dynamicsWorld = null; + private BroadphaseInterface broadphase; + private BroadphaseType broadphaseType = BroadphaseType.DBVT; + private CollisionDispatcher dispatcher; + private ConstraintSolver solver; + private DefaultCollisionConfiguration collisionConfiguration; +// private Map physicsGhostNodes = new ConcurrentHashMap(); + private Map physicsNodes = new ConcurrentHashMap(); + private List physicsJoints = new LinkedList(); + private List collisionListeners = new LinkedList(); + private List collisionEvents = new LinkedList(); + private Map collisionGroupListeners = new ConcurrentHashMap(); + private ConcurrentLinkedQueue tickListeners = new ConcurrentLinkedQueue(); + 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 running on this thread
+ * 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 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 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
+ * @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 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 Future enqueueOnThisThread(Callable callable) { + AppTask task = new AppTask(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 + * @param callable + * @return + */ + public Future enqueue(Callable callable) { + AppTask task = new AppTask(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 joints = physicsNode.getJoints(); + for (Iterator 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 children = ((Node) spatial).getChildren(); + for (Iterator 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 joints = physicsNode.getJoints(); + for (Iterator 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 children = ((Node) spatial).getChildren(); + for (Iterator it = children.iterator(); it.hasNext();) { + Spatial spat = it.next(); + if (spat instanceof Node) { + removeAll((Node) spat); + } + } + } + } + + private void addGhostObject(PhysicsGhostObject node) { + dynamicsWorld.addCollisionObject(node.getObjectId()); + } + + private void removeGhostObject(PhysicsGhostObject node) { + dynamicsWorld.removeCollisionObject(node.getObjectId()); + } + + private void addCharacter(PhysicsCharacter node) { +// 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) { + dynamicsWorld.removeAction(node.getControllerId()); + dynamicsWorld.removeCollisionObject(node.getObjectId()); + } + + private void addRigidBody(PhysicsRigidBody node) { + physicsNodes.put(node.getObjectId(), node); + dynamicsWorld.addRigidBody(node.getObjectId()); + if (node instanceof PhysicsVehicle) { + ((PhysicsVehicle) node).createVehicle(this); + dynamicsWorld.addVehicle(((PhysicsVehicle) node).getVehicleId()); + } + } + + private void removeRigidBody(PhysicsRigidBody node) { + if (node instanceof PhysicsVehicle) { + dynamicsWorld.removeVehicle(((PhysicsVehicle) node).getVehicleId()); + } + physicsNodes.remove(node.getObjectId()); + dynamicsWorld.removeRigidBody(node.getObjectId()); + } + + private void addJoint(PhysicsJoint joint) { + physicsJoints.add(joint); + dynamicsWorld.addConstraint(joint.getConstraint(), !joint.isCollisionBetweenLinkedBodys()); + } + + private void removeJoint(PhysicsJoint joint) { + physicsJoints.remove(joint); + dynamicsWorld.removeConstraint(joint.getConstraint()); + } + + /** + * 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.
+ * 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(); + 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 rayTest(Vector3f from, Vector3f to, List 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 results; + + public InternalRayListener(List 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
+ * 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 sweepTest(CollisionShape shape, Transform start, Transform end){ + List results = new LinkedList(); + 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
+ * 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 sweepTest(CollisionShape shape, Transform start, Transform end, List 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 results; + + public InternalSweepListener(List 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 deterministic mode of this physics space. + * If the physicsSpace is deterministic, low fps values will + * be compensated by stepping the physics space multiple times per frame. + * If not, low fps values will make the physics inaccurate. Default is false. + * @param deterministic + * @deprecated in favor of PhysicsSpace.setMaxSubSteps + */ + @Deprecated + public void setDeterministic(boolean deterministic) { + if (!deterministic) { + maxSubSteps = 1; + } else { + maxSubSteps = 4; + } + } + + /** + * 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
+ * @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/PhysicsTickListener.java b/engine/src/jbullet/com/jme3/bullet/PhysicsTickListener.java new file mode 100644 index 000000000..0f3bbca8c --- /dev/null +++ b/engine/src/jbullet/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/jbullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java new file mode 100644 index 000000000..28e15347a --- /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.math.Vector3f; +import com.jme3.bullet.util.Converter; +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 000000000..cf8d8ae2b --- /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 eventBuffer = new ConcurrentLinkedQueue(); + + 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/PhysicsCollisionGroupListener.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java new file mode 100644 index 000000000..739598c25 --- /dev/null +++ b/engine/src/jbullet/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, called from physics thread.
+ * This is only called when the collision will happen based on the collisionGroup and collideWithGroups + * settings in the PhysicsCollisionObject. That is the case when one of the partys has the + * collisionGroup of the other in its collideWithGroups set.
+ * @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/jbullet/com/jme3/bullet/collision/PhysicsCollisionListener.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionListener.java new file mode 100644 index 000000000..2739c04dc --- /dev/null +++ b/engine/src/jbullet/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, called from render thread.
+ * 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/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java new file mode 100644 index 000000000..4eab96e61 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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.
+ * 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.
+ * Two object will collide when one 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.
+ * Two object will collide when one of the partys has the + * collisionGroup of the other in its collideWithGroups set.
+ * @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 collisionGroup + */ + 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
+ * Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging + * @param manager AssetManager to load the default wireframe material for the debug shape + * @deprecated in favor of PhysicsSpace.enableDebug(AssetManager manager); + */ + @Deprecated + public Spatial attachDebugShape(AssetManager manager) { + debugMaterialBlue = new Material(manager, "Common/MatDefs/Misc/WireColor.j3md"); + debugMaterialBlue.setColor("Color", ColorRGBA.Blue); + debugMaterialGreen = new Material(manager, "Common/MatDefs/Misc/WireColor.j3md"); + debugMaterialGreen.setColor("Color", ColorRGBA.Green); + debugMaterialRed = new Material(manager, "Common/MatDefs/Misc/WireColor.j3md"); + debugMaterialRed.setColor("Color", ColorRGBA.Red); + debugMaterialYellow = new Material(manager, "Common/MatDefs/Misc/WireColor.j3md"); + debugMaterialYellow.setColor("Color", ColorRGBA.Yellow); + debugArrow = new Arrow(Vector3f.UNIT_XYZ); + debugArrowGeom = new Geometry("DebugArrow", debugArrow); + debugArrowGeom.setMaterial(debugMaterialGreen); + return attachDebugShape(); + } + + /** + * @deprecated in favor of PhysicsSpace.enableDebug(AssetManager manager); + */ + @Deprecated + public 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
+ * Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging + * @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 children = ((Node) spatial).getChildren(); + for (Iterator 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 000000000..1941344c3 --- /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 000000000..d513204eb --- /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 000000000..1dbc370d0 --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +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 000000000..61024ebbe --- /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 000000000..65c44001c --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CollisionShape.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.bullet.collision.shapes; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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 000000000..976d30d2f --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +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 children = new ArrayList(); + + 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 it = children.iterator(); it.hasNext();) { + ChildCollisionShape childCollisionShape = it.next(); + if (childCollisionShape.shape == shape) { + it.remove(); + } + } + } + + public List 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()); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + children = capsule.readSavableArrayList("children", new ArrayList()); + cShape.setLocalScaling(Converter.convert(getScale())); + cShape.setMargin(margin); + loadChildren(); + } + + private void loadChildren() { + for (Iterator 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 000000000..7103f0dad --- /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 000000000..884d7f0ac --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +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 000000000..ccf688dc8 --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +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 000000000..7f0611e2f --- /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 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 000000000..c27945d2c --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java @@ -0,0 +1,77 @@ +package com.jme3.bullet.collision.shapes; + +import java.nio.FloatBuffer; + +import javax.vecmath.Vector3f; + +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; + +public class HullCollisionShape extends CollisionShape { + + private float[] points; + + public HullCollisionShape() { + } + + public HullCollisionShape(Mesh mesh) { + this.points = getPoints(mesh); + 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 pointList = new ObjectArrayList(); + 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 000000000..264d253f3 --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +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 000000000..ce51f18a7 --- /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 000000000..7ddefd3f7 --- /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 000000000..787f59704 --- /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/collision/shapes/infos/ChildCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java new file mode 100644 index 000000000..cc721d0f1 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java @@ -0,0 +1,50 @@ +/* + * 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +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/jbullet/com/jme3/bullet/control/CharacterControl.java b/engine/src/jbullet/com/jme3/bullet/control/CharacterControl.java new file mode 100644 index 000000000..2acac5316 --- /dev/null +++ b/engine/src/jbullet/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/jbullet/com/jme3/bullet/control/GhostControl.java b/engine/src/jbullet/com/jme3/bullet/control/GhostControl.java new file mode 100644 index 000000000..99e598480 --- /dev/null +++ b/engine/src/jbullet/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/jbullet/com/jme3/bullet/control/PhysicsControl.java b/engine/src/jbullet/com/jme3/bullet/control/PhysicsControl.java new file mode 100644 index 000000000..a0b94be79 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/control/PhysicsControl.java @@ -0,0 +1,19 @@ +/* + * 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(); +} diff --git a/engine/src/jbullet/com/jme3/bullet/control/RagdollControl.java b/engine/src/jbullet/com/jme3/bullet/control/RagdollControl.java new file mode 100644 index 000000000..90ad9181f --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/control/RagdollControl.java @@ -0,0 +1,297 @@ +package com.jme3.bullet.control; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.joints.ConeJoint; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.FastMath; +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 com.jme3.util.TempVars; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class RagdollControl implements PhysicsControl { + + protected static final Logger logger = Logger.getLogger(RagdollControl.class.getName()); + protected List boneLinks = new LinkedList(); + protected Skeleton skeleton; + protected PhysicsSpace space; + protected boolean enabled = true; + protected boolean debug = false; + protected Quaternion tmp_jointRotation = new Quaternion(); + protected PhysicsRigidBody baseRigidBody; + + public RagdollControl() { + } + + public void update(float tpf) { + if (!enabled) { + return; + } + TempVars vars = TempVars.get(); + assert vars.lock(); + + skeleton.reset(); + for (PhysicsBoneLink link : boneLinks) { + Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); + Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); + + q.toAxes(vars.tri); + + Vector3f dir = vars.tri[2]; + float len = link.length; + + Vector3f parentPos = new Vector3f(p).subtractLocal(dir.mult(len / 2f)); + Vector3f childPos = new Vector3f(p).addLocal(dir.mult(len / 2f)); + + Quaternion q2 = q.clone(); + Quaternion rot = new Quaternion(); + rot.fromAngles(FastMath.HALF_PI, 0, 0); + q2.multLocal(rot); + q2.normalize(); + + link.parentBone.setUserTransformsWorld(parentPos, q2); + if (link.childBone.getChildren().size() == 0) { + link.childBone.setUserTransformsWorld(childPos, q2.clone()); + } + } + assert vars.unlock(); + } + + public Control cloneForSpatial(Spatial spatial) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void setSpatial(Spatial model) { + removeFromPhysicsSpace(); + clearData(); + // put into bind pose and compute bone transforms in model space + // maybe dont reset to ragdoll out of animations? + scanSpatial(model); + + logger.log(Level.INFO, "Create physics ragdoll for skeleton {0}", skeleton); + } + + public void addBoneName(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + private void scanSpatial(Spatial model) { + AnimControl animControl = model.getControl(AnimControl.class); + + skeleton = animControl.getSkeleton(); + skeleton.resetAndUpdate(); + for (int i = 0; i < skeleton.getBoneCount(); i++) { + Bone childBone = skeleton.getBone(i); + childBone.setUserControl(true); + if (childBone.getParent() == null) { + Vector3f parentPos = childBone.getModelSpacePosition().add(model.getWorldTranslation()); + logger.log(Level.INFO, "Found root bone in skeleton {0}", skeleton); + baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(.1f)), 1); + baseRigidBody.setPhysicsLocation(parentPos); + boneLinks = boneRecursion(model, childBone, baseRigidBody, boneLinks, 1); + return; + } + + } +// BoneAnimation myAnimation = new BoneAnimation("boneAnimation", 1000000); +// myAnimation.setTracks(new BoneTrack[0]); +// animControl.addAnim(myAnimation); +// animControl.createChannel().setAnim("boneAnimation"); + + } + + private List boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, List list, int reccount) { + ArrayList children = bone.getChildren(); + bone.setUserControl(true); + for (Iterator it = children.iterator(); it.hasNext();) { + Bone childBone = it.next(); + Bone parentBone = bone; + Vector3f parentPos = parentBone.getModelSpacePosition().add(model.getWorldTranslation()); + Vector3f childPos = childBone.getModelSpacePosition().add(model.getWorldTranslation()); + //get location between the two bones (physicscapsule center) + Vector3f jointCenter = parentPos.add(childPos).multLocal(0.5f); + tmp_jointRotation.lookAt(childPos.subtract(parentPos), Vector3f.UNIT_Y); + // length of the joint + float height = parentPos.distance(childPos); + + // TODO: joints act funny when bone is too thin?? + float radius = height > 2f ? 0.4f : height * .2f; + CapsuleCollisionShape shape = new CapsuleCollisionShape(radius, height - (radius), 2); + + PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, 10.0f / (float) reccount); + shapeNode.setPhysicsLocation(jointCenter); + shapeNode.setPhysicsRotation(tmp_jointRotation.toRotationMatrix()); + + PhysicsBoneLink link = new PhysicsBoneLink(); + link.parentBone = parentBone; + link.childBone = childBone; + link.rigidBody = shapeNode; + link.length = height; + + //TODO: ragdoll mass 1 + if (parent != null) { + //get length of parent + float parentHeight = 0.0f; + if (bone.getParent() != null) { + parentHeight = bone.getParent().getModelSpacePosition().add(model.getWorldTranslation()).distance(parentPos); + } + //local position from parent + link.pivotA = new Vector3f(0, 0, (parentHeight * .5f)); + //local position from child + link.pivotB = new Vector3f(0, 0, -(height * .5f)); + + ConeJoint joint = new ConeJoint(parent, shapeNode, link.pivotA, link.pivotB); + joint.setLimit(FastMath.HALF_PI, FastMath.HALF_PI, 0.01f); + + link.joint = joint; + joint.setCollisionBetweenLinkedBodys(false); + } + list.add(link); + boneRecursion(model, childBone, shapeNode, list, reccount++); + } + return list; + } + + private void clearData() { + boneLinks.clear(); + baseRigidBody = null; + } + + private void addToPhysicsSpace() { + if (baseRigidBody != null) { + space.add(baseRigidBody); + } + for (Iterator it = boneLinks.iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + if (physicsBoneLink.rigidBody != null) { + space.add(physicsBoneLink.rigidBody); + } + } + for (Iterator it = boneLinks.iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + if (physicsBoneLink.joint != null) { + space.add(physicsBoneLink.joint); + } + } + } + + private void removeFromPhysicsSpace() { + if (baseRigidBody != null) { + space.remove(baseRigidBody); + } + for (Iterator it = boneLinks.iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + if (physicsBoneLink.joint != null) { + space.remove(physicsBoneLink.joint); + } + } + for (Iterator it = boneLinks.iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + if (physicsBoneLink.rigidBody != null) { + space.remove(physicsBoneLink.rigidBody); + } + } + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if(!enabled&&space!=null){ + removeFromPhysicsSpace(); + }else if(enabled && space!=null){ + addToPhysicsSpace(); + } + } + + public boolean isEnabled() { + return enabled; + } + + public void attachDebugShape(AssetManager manager) { + for (Iterator it = boneLinks.iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + physicsBoneLink.rigidBody.attachDebugShape(manager); + } + debug = true; + } + + public void detachDebugShape() { + for (Iterator it = boneLinks.iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + physicsBoneLink.rigidBody.detachDebugShape(); + } + debug = false; + } + + public void render(RenderManager rm, ViewPort vp) { + if (enabled && space != null && space.getDebugManager() != null) { + if (!debug) { + attachDebugShape(space.getDebugManager()); + } + for (Iterator it = boneLinks.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); + } + } + } + } + + public void setPhysicsSpace(PhysicsSpace space) { + if (space == null) { + removeFromPhysicsSpace(); + this.space = space; + } else { + if (this.space == space) { + return; + } + this.space = space; + addToPhysicsSpace(); + } + } + + public PhysicsSpace getPhysicsSpace() { + return space; + } + + public void write(JmeExporter ex) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void read(JmeImporter im) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + private static class PhysicsBoneLink { + + Bone childBone; + Bone parentBone; + PhysicsJoint joint; + PhysicsRigidBody rigidBody; + Vector3f pivotA; + Vector3f pivotB; + float length; + } +} diff --git a/engine/src/jbullet/com/jme3/bullet/control/RigidBodyControl.java b/engine/src/jbullet/com/jme3/bullet/control/RigidBodyControl.java new file mode 100644 index 000000000..23fafb274 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/control/RigidBodyControl.java @@ -0,0 +1,280 @@ +/* + * 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.Node; +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 + * @param child + * @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) { + Node parent = spatial.getParent(); + if (parent != null) { + spatial.removeFromParent(); + } + collisionShape = CollisionShapeFactory.createDynamicMeshShape(spatial); + if (parent != null) { + parent.attachChild(spatial); + } + } else { + Node parent = spatial.getParent(); + if (parent != null) { + spatial.removeFromParent(); + } + collisionShape = CollisionShapeFactory.createMeshShape(spatial); + if (parent != null) { + parent.attachChild(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 + * @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/jbullet/com/jme3/bullet/control/VehicleControl.java b/engine/src/jbullet/com/jme3/bullet/control/VehicleControl.java new file mode 100644 index 000000000..4e6f416e4 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/control/VehicleControl.java @@ -0,0 +1,270 @@ +/* + * 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.joints.PhysicsJoint; +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 child + * @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 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 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 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/jbullet/com/jme3/bullet/joints/ConeJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/ConeJoint.java new file mode 100644 index 000000000..243c52e94 --- /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.math.Matrix3f; +import com.jme3.math.Vector3f; +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 java.io.IOException; + +/** + * From bullet manual:
+ * 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 000000000..27e267eb3 --- /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.math.Vector3f; +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 java.io.IOException; + +/** + * From bullet manual:
+ * 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 000000000..728a3c660 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/joints/PhysicsJoint.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.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.TypedConstraint; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + *

PhysicsJoint - Basic Phyiscs Joint

+ * @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 getConstraint() { + return constraint; + } + + /** + * @return the collisionBetweenLinkedBodys + */ + public boolean isCollisionBetweenLinkedBodys() { + return collisionBetweenLinkedBodys; + } + + /** + * toggles collisions between linked bodys
+ * 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 000000000..2e93d2b88 --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * From bullet manual:
+ * 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 000000000..492df5e79 --- /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.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +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.OutputCapsule; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * From bullet manual:
+ * 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 rotationalMotors = new LinkedList(); + 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 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 000000000..c52eefb15 --- /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.export.JmeExporter; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * From bullet manual:
+ * 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 000000000..b9df96d63 --- /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 000000000..0be531140 --- /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.math.Vector3f; +import com.jme3.bullet.util.Converter; + +/** + * + * @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/nodes/PhysicsBaseNode.java b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsBaseNode.java new file mode 100644 index 000000000..cac21ecab --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsBaseNode.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.bullet.nodes; + +import com.jme3.asset.AssetManager; +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.material.Material; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import java.io.IOException; + +/** + * Base class for Physics Nodes (PhysicsNode, PhysicsGhostNode) + * @author normenhansen + * @deprecated in favor of physics Controls + */ +@Deprecated +public abstract class PhysicsBaseNode extends Node { + + protected PhysicsCollisionObject collisionObject; + protected Quaternion tmp_inverseWorldRotation = new Quaternion(); + + public void updatePhysicsState() { + } + + /** + * 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) { + collisionObject.setCollisionShape(collisionShape); + } + + /** + * @return the CollisionShape of this PhysicsNode, to be able to reuse it with + * other physics nodes (increases performance) + */ + public CollisionShape getCollisionShape() { + return collisionObject.getCollisionShape(); + } + + /** + * Returns the collision group for this collision shape + * @return + */ + public int getCollisionGroup() { + return collisionObject.getCollisionGroup(); + } + + /** + * Sets the collision group number for this physics object.
+ * 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.
+ * Two object will collide when one of the partys has the + * collisionGroup of the other in its collideWithGroups set. + * @param collisionGroup the collisionGroup to set + */ + public void setCollisionGroup(int collisionGroup) { + collisionObject.setCollisionGroup(collisionGroup); + } + + /** + * Add a group that this object will collide with.
+ * Two object will collide when one of the partys has the + * collisionGroup of the other in its collideWithGroups set.
+ * @param collisionGroup + */ + public void addCollideWithGroup(int collisionGroup) { + collisionObject.addCollideWithGroup(collisionGroup); + } + + /** + * Remove a group from the list this object collides with. + * @param collisionGroup + */ + public void removeCollideWithGroup(int collisionGroup) { + collisionObject.removeCollideWithGroup(collisionGroup); + } + + /** + * Directly set the bitmask for collision groups that this object collides with. + * @param collisionGroup + */ + public void setCollideWithGroups(int collisionGroups) { + collisionObject.setCollideWithGroups(collisionGroups); + } + + /** + * Gets the bitmask of collision groups that this object collides with. + * @return + */ + public int getCollideWithGroups() { + return collisionObject.getCollideWithGroups(); + } + + /** + * computes the local translation from the parameter translation and sets it as new + * local translation
+ * This should only be called from the physics thread to update the jme spatial + * @param translation new world translation of this spatial. + * @return the computed local translation + */ + public Vector3f setWorldTranslation(Vector3f translation) { + Vector3f localTranslation = super.getLocalTranslation(); + if (parent != null) { + localTranslation.set(translation).subtractLocal(parent.getWorldTranslation()); + localTranslation.divideLocal(parent.getWorldScale()); + tmp_inverseWorldRotation.set(parent.getWorldRotation()).inverseLocal().multLocal(localTranslation); + } else { + localTranslation.set(translation); + } + super.setLocalTranslation(localTranslation); + return localTranslation; + } + + /** + * computes the local rotation from the parameter rot and sets it as new + * local rotation
+ * This should only be called from the physics thread to update the jme spatial + * @param rot new world rotation of this spatial. + * @return the computed local rotation + */ + public Quaternion setWorldRotation(Quaternion rot) { + Quaternion localRotation = super.getLocalRotation(); + if (parent != null) { + tmp_inverseWorldRotation.set(parent.getWorldRotation()).inverseLocal().mult(rot, localRotation); + } else { + localRotation.set(rot); + } + super.setLocalRotation(localRotation); + return localRotation; + } + + /** + * Attaches a visual debug shape of the current collision shape to this physics object
+ * Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging + * @param manager AssetManager to load the default wireframe material for the debug shape + */ + public void attachDebugShape(AssetManager manager) { + collisionObject.attachDebugShape(manager); + } + + public void attachDebugShape(Material material) { + collisionObject.attachDebugShape(material); + } + + /** + * Detaches the debug shape + */ + public void detachDebugShape() { + collisionObject.detachDebugShape(); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(collisionObject, "collisionObject", null); + + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + collisionObject = (PhysicsCollisionObject) capsule.readSavable("collisionObject", null); + } +} diff --git a/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsCharacterNode.java b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsCharacterNode.java new file mode 100644 index 000000000..c2bc80faa --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsCharacterNode.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.bullet.nodes; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.objects.PhysicsCharacter; +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.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import java.io.IOException; + +/** + * + * @author normenhansen + * @deprecated in favor of physics Controls + */ +@Deprecated +public class PhysicsCharacterNode extends PhysicsBaseNode { + + public PhysicsCharacterNode() { + } + + public PhysicsCharacterNode(CollisionShape shape, float stepHeight) { + collisionObject = new CharacterControl(shape, stepHeight); + addControl((CharacterControl)collisionObject); + } + + public PhysicsCharacterNode(Spatial spat, CollisionShape shape, float stepHeight) { + collisionObject = new CharacterControl(shape, stepHeight); + addControl((CharacterControl)collisionObject); + attachChild(spat); + } + + public void warp(Vector3f location) { + ((PhysicsCharacter)collisionObject).warp(location); + } + + @Override + public void setLocalTransform(Transform t) { + super.setLocalTransform(t); + ((CharacterControl)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalTranslation(Vector3f localTranslation) { + super.setLocalTranslation(localTranslation); + ((CharacterControl)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalTranslation(float x, float y, float z) { + super.setLocalTranslation(x, y, z); + ((CharacterControl)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalRotation(Matrix3f rotation) { + super.setLocalRotation(rotation); + } + + @Override + public void setLocalRotation(Quaternion quaternion) { + super.setLocalRotation(quaternion); + } + + /** + * set the walk direction, works continuously + * @param vec the walk direction to set + */ + public void setWalkDirection(Vector3f vec) { + ((PhysicsCharacter)collisionObject).setWalkDirection(vec); + } + + public void setUpAxis(int axis) { + ((PhysicsCharacter)collisionObject).setUpAxis(axis); + } + + public int getUpAxis() { + return ((PhysicsCharacter)collisionObject).getUpAxis(); + } + + public void setFallSpeed(float fallSpeed) { + ((PhysicsCharacter)collisionObject).setFallSpeed(fallSpeed); + } + + public float getFallSpeed() { + return ((PhysicsCharacter)collisionObject).getFallSpeed(); + } + + public void setJumpSpeed(float jumpSpeed) { + ((PhysicsCharacter)collisionObject).setJumpSpeed(jumpSpeed); + } + + public float getJumpSpeed() { + return ((PhysicsCharacter)collisionObject).getJumpSpeed(); + } + + public void setGravity(float value) { + ((PhysicsCharacter)collisionObject).setGravity(value); + } + + public float getGravity() { + return ((PhysicsCharacter)collisionObject).getGravity(); + } + + public void setMaxSlope(float slopeRadians) { + ((PhysicsCharacter)collisionObject).setMaxSlope(slopeRadians); + } + + public float getMaxSlope() { + return ((PhysicsCharacter)collisionObject).getMaxSlope(); + } + + public boolean onGround() { + return ((PhysicsCharacter)collisionObject).onGround(); + } + + public void jump() { + ((PhysicsCharacter)collisionObject).jump(); + } + + public void setCollisionShape(CollisionShape collisionShape) { + ((PhysicsCharacter)collisionObject).setCollisionShape(collisionShape); + } + + public PhysicsCharacter getPhysicsCharacter() { + return ((PhysicsCharacter)collisionObject); + } + + public void destroy() { + ((PhysicsCharacter)collisionObject).destroy(); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + } +} diff --git a/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsGhostNode.java b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsGhostNode.java new file mode 100644 index 000000000..6d456aa37 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsGhostNode.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.bullet.nodes; + +import com.jme3.scene.Spatial; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.GhostControl; +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.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.List; + +/** + * From Bullet manual:
+ * 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.
+ * @author normenhansen + * @deprecated in favor of physics Controls + */ +@Deprecated +public class PhysicsGhostNode extends PhysicsBaseNode { + +// protected PhysicsGhostControl gObject; + + public PhysicsGhostNode() { + } + + public PhysicsGhostNode(CollisionShape shape) { + collisionObject=new GhostControl(shape); + addControl(((GhostControl)collisionObject)); + } + + public PhysicsGhostNode(Spatial child, CollisionShape shape) { + collisionObject=new GhostControl(shape); + addControl(((GhostControl)collisionObject)); + attachChild(child); + } + + @Override + public void setCollisionShape(CollisionShape collisionShape) { + ((GhostControl)collisionObject).setCollisionShape(collisionShape); + } + + @Override + public void setLocalTransform(Transform t) { + super.setLocalTransform(t); + ((PhysicsGhostObject)collisionObject).setPhysicsLocation(getWorldTranslation()); + ((PhysicsGhostObject)collisionObject).setPhysicsRotation(getWorldRotation().toRotationMatrix()); + } + + @Override + public void setLocalTranslation(Vector3f localTranslation) { + super.setLocalTranslation(localTranslation); + ((PhysicsGhostObject)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalTranslation(float x, float y, float z) { + super.setLocalTranslation(x, y, z); + ((PhysicsGhostObject)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalRotation(Matrix3f rotation) { + super.setLocalRotation(rotation); + ((PhysicsGhostObject)collisionObject).setPhysicsRotation(getWorldRotation().toRotationMatrix()); + } + + @Override + public void setLocalRotation(Quaternion quaternion) { + super.setLocalRotation(quaternion); + ((PhysicsGhostObject)collisionObject).setPhysicsRotation(getWorldRotation().toRotationMatrix()); + } + + /** + * used internally + */ + public PhysicsGhostObject getGhostObject() { + return ((GhostControl)collisionObject); + } + + /** + * destroys this PhysicsGhostNode and removes it from memory + */ + public void destroy() { + ((GhostControl)collisionObject).destroy(); + } + + /** + * Another Object is overlapping with this GhostNode, + * if and if only there CollisionShapes overlaps. + * They could be both regular PhysicsNodes or PhysicsGhostNode. + * @return All CollisionObjects overlapping with this GhostNode. + */ + public List getOverlappingObjects() { + return ((GhostControl)collisionObject).getOverlappingObjects(); + } + + /** + * + * @return With how many other CollisionObjects this GhostNode is currently overlapping. + */ + public int getOverlappingCount() { + return ((GhostControl)collisionObject).getOverlappingCount(); + } + + /** + * + * @param index The index of the overlapping Node to retrieve. + * @return The Overlapping CollisionObject at the given index. + */ + public PhysicsCollisionObject getOverlapping(int index) { + return ((GhostControl)collisionObject).getOverlapping(index); + } + + public void setCcdSweptSphereRadius(float radius) { + ((GhostControl)collisionObject).setCcdSweptSphereRadius(radius); + } + + public void setCcdMotionThreshold(float threshold) { + ((GhostControl)collisionObject).setCcdMotionThreshold(threshold); + } + + public float getCcdSweptSphereRadius() { + return ((GhostControl)collisionObject).getCcdSweptSphereRadius(); + } + + public float getCcdMotionThreshold() { + return ((GhostControl)collisionObject).getCcdMotionThreshold(); + } + + public float getCcdSquareMotionThreshold() { + return ((GhostControl)collisionObject).getCcdSquareMotionThreshold(); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + } +} diff --git a/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsNode.java b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsNode.java new file mode 100644 index 000000000..b99cf9c1d --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsNode.java @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.nodes; + +import com.jme3.asset.AssetManager; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.PhysicsJoint; +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.material.Material; +import com.jme3.math.Matrix3f; +import java.io.IOException; +import java.util.List; + +/** + *

PhysicsNode - Basic physics object

+ * @author normenhansen + * @deprecated in favor of physics Controls + */ +@Deprecated +public class PhysicsNode extends PhysicsBaseNode { + + protected Vector3f continuousForce = new Vector3f(); + protected Vector3f continuousForceLocation = new Vector3f(); + protected Vector3f continuousTorque = new Vector3f(); + protected boolean applyForce = false; + protected boolean applyTorque = false; + + public PhysicsNode() { + } + + /** + * Creates a new PhysicsNode with the supplied collision shape + * @param child + * @param shape + */ + public PhysicsNode(CollisionShape shape) { + collisionObject = new RigidBodyControl(shape); + addControl(((RigidBodyControl)collisionObject)); + } + + public PhysicsNode(CollisionShape shape, float mass) { + collisionObject = new RigidBodyControl(shape, mass); + addControl(((RigidBodyControl)collisionObject)); + } + + /** + * Creates a new PhysicsNode with the supplied child node or geometry and + * sets the supplied collision shape to the PhysicsNode + * @param child + * @param shape + */ + public PhysicsNode(Spatial child, CollisionShape shape) { + this(child, shape, 1.0f); + } + + /** + * Creates a new PhysicsNode with the supplied child node or geometry and + * uses the supplied collision shape for that PhysicsNode
+ * @param child + * @param shape + */ + public PhysicsNode(Spatial child, CollisionShape shape, float mass) { + collisionObject = new RigidBodyControl(shape, mass); + addControl(((RigidBodyControl)collisionObject)); + attachChild(child); + } + + @Override + public void setLocalTransform(Transform t) { + super.setLocalTransform(t); + ((PhysicsRigidBody)collisionObject).setPhysicsLocation(getWorldTranslation()); + ((PhysicsRigidBody)collisionObject).setPhysicsRotation(getWorldRotation().toRotationMatrix()); + } + + @Override + public void setLocalTranslation(Vector3f localTranslation) { + super.setLocalTranslation(localTranslation); + ((PhysicsRigidBody)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalTranslation(float x, float y, float z) { + super.setLocalTranslation(x, y, z); + ((PhysicsRigidBody)collisionObject).setPhysicsLocation(getWorldTranslation()); + } + + @Override + public void setLocalRotation(Matrix3f rotation) { + super.setLocalRotation(rotation); + ((PhysicsRigidBody)collisionObject).setPhysicsRotation(getWorldRotation().toRotationMatrix()); + } + + @Override + public void setLocalRotation(Quaternion quaternion) { + super.setLocalRotation(quaternion); + ((PhysicsRigidBody)collisionObject).setPhysicsRotation(getWorldRotation().toRotationMatrix()); + } + + /** + * This is normally only needed when using detached physics + * @param location the location of the actual physics object + */ + public void setPhysicsLocation(Vector3f location) { + ((PhysicsRigidBody)collisionObject).setPhysicsLocation(location); + } + + /** + * This is normally only needed when using detached physics + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Matrix3f rotation) { + ((PhysicsRigidBody)collisionObject).setPhysicsRotation(rotation); + } + + /** + * This is normally only needed when using detached physics + * @param location the location of the actual physics object is stored in this Vector3f + */ + public void getPhysicsLocation(Vector3f location) { + ((PhysicsRigidBody)collisionObject).getPhysicsLocation(location); + } + + /** + * This is normally only needed when using detached physics + * @param rotation the rotation of the actual physics object is stored in this Matrix3f + */ + public void getPhysicsRotation(Matrix3f rotation) { + ((PhysicsRigidBody)collisionObject).getPhysicsRotationMatrix(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) { + ((PhysicsRigidBody)collisionObject).setKinematic(kinematic); + } + + public boolean isKinematic() { + return ((PhysicsRigidBody)collisionObject).isKinematic(); + } + + public void setCcdSweptSphereRadius(float radius) { + ((PhysicsRigidBody)collisionObject).setCcdSweptSphereRadius(radius); + } + + /** + * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection
+ * Set to zero to disable (default) + * @param threshold + */ + public void setCcdMotionThreshold(float threshold) { + ((PhysicsRigidBody)collisionObject).setCcdMotionThreshold(threshold); + } + + public float getCcdSweptSphereRadius() { + return ((PhysicsRigidBody)collisionObject).getCcdSweptSphereRadius(); + } + + public float getCcdMotionThreshold() { + return ((PhysicsRigidBody)collisionObject).getCcdMotionThreshold(); + } + + public float getCcdSquareMotionThreshold() { + return ((PhysicsRigidBody)collisionObject).getCcdSquareMotionThreshold(); + } + + public float getMass() { + return ((PhysicsRigidBody)collisionObject).getMass(); + } + + /** + * Sets the mass of this PhysicsNode, objects with mass=0 are static. + * @param mass + */ + public void setMass(float mass) { + ((PhysicsRigidBody)collisionObject).setMass(mass); + } + + public Vector3f getGravity() { + return ((PhysicsRigidBody)collisionObject).getGravity(); + } + + public Vector3f getGravity(Vector3f gravity) { + return ((PhysicsRigidBody)collisionObject).getGravity(gravity); + } + + /** + * Set the local gravity of this PhysicsNode
+ * 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) { + ((PhysicsRigidBody)collisionObject).setGravity(gravity); + } + + public float getFriction() { + return ((PhysicsRigidBody)collisionObject).getFriction(); + } + + /** + * Sets the friction of this physics object + * @param friction the friction of this physics object + */ + public void setFriction(float friction) { + ((PhysicsRigidBody)collisionObject).setFriction(friction); + } + + public void setDamping(float linearDamping, float angularDamping) { + ((PhysicsRigidBody)collisionObject).setDamping(linearDamping, angularDamping); + } + + public void setLinearDamping(float linearDamping) { + ((PhysicsRigidBody)collisionObject).setLinearDamping(linearDamping); + } + + public void setAngularDamping(float angularDamping) { + ((PhysicsRigidBody)collisionObject).setAngularDamping(angularDamping); + } + + public float getLinearDamping() { + return ((PhysicsRigidBody)collisionObject).getLinearDamping(); + } + + public float getAngularDamping() { + return ((PhysicsRigidBody)collisionObject).getAngularDamping(); + } + + public float getRestitution() { + return ((PhysicsRigidBody)collisionObject).getRestitution(); + } + + /** + * The "bouncyness" of the PhysicsNode, best performance if restitution=0 + * @param restitution + */ + public void setRestitution(float restitution) { + ((PhysicsRigidBody)collisionObject).setRestitution(restitution); + } + + /** + * Get the current angular velocity of this PhysicsNode + * @return the current linear velocity + */ + public Vector3f getAngularVelocity() { + return ((PhysicsRigidBody)collisionObject).getAngularVelocity(); + } + + /** + * Get the current angular velocity of this PhysicsNode + * @param vec the vector to store the velocity in + */ + public void getAngularVelocity(Vector3f vec) { + ((PhysicsRigidBody)collisionObject).getAngularVelocity(vec); + } + + /** + * Sets the angular velocity of this PhysicsNode + * @param vec the angular velocity of this PhysicsNode + */ + public void setAngularVelocity(Vector3f vec) { + ((PhysicsRigidBody)collisionObject).setAngularVelocity(vec); + } + + /** + * Get the current linear velocity of this PhysicsNode + * @return the current linear velocity + */ + public Vector3f getLinearVelocity() { + return ((PhysicsRigidBody)collisionObject).getLinearVelocity(); + } + + /** + * Get the current linear velocity of this PhysicsNode + * @param vec the vector to store the velocity in + */ + public void getLinearVelocity(Vector3f vec) { + ((PhysicsRigidBody)collisionObject).getLinearVelocity(vec); + } + + /** + * Sets the linear velocity of this PhysicsNode + * @param vec the linear velocity of this PhysicsNode + */ + public void setLinearVelocity(Vector3f vec) { + ((PhysicsRigidBody)collisionObject).setLinearVelocity(vec); + } + + @Override + public void updateLogicalState(float tpf) { + super.updateLogicalState(tpf); + if (applyForce) { + ((PhysicsRigidBody)collisionObject).applyForce(continuousForce,continuousForceLocation); + } + if (applyTorque) { + ((PhysicsRigidBody)collisionObject).applyTorque(continuousTorque); + } + } + + /** + * Get the currently applied continuous force + * @param vec the vector to store the continuous force in + * @return null if no force is applied + */ + public synchronized Vector3f getContinuousForce(Vector3f vec) { + if (applyForce) { + return vec.set(continuousForce); + } else { + return null; + } + } + + /** + * get the currently applied continuous force + * @return null if no force is applied + */ + public synchronized Vector3f getContinuousForce() { + if (applyForce) { + return continuousForce; + } else { + return null; + } + } + + /** + * Get the currently applied continuous force location + * @return null if no force is applied + */ + public synchronized Vector3f getContinuousForceLocation() { + if (applyForce) { + return continuousForceLocation; + } else { + return null; + } + } + + /** + * Apply a continuous force to this PhysicsNode, the force is updated automatically each + * tick so you only need to set it once and then set it to false to stop applying + * the force. + * @param apply true if the force should be applied each physics tick + * @param force the vector of the force to apply + */ + public synchronized void applyContinuousForce(boolean apply, Vector3f force) { + if (force != null) { + continuousForce.set(force); + } + continuousForceLocation.set(0, 0, 0); + applyForce = apply; + + } + + /** + * Apply a continuous force to this PhysicsNode, the force is updated automatically each + * tick so you only need to set it once and then set it to false to stop applying + * the force. + * @param apply true if the force should be applied each physics tick + * @param force the offset of the force + */ + public synchronized void applyContinuousForce(boolean apply, Vector3f force, Vector3f location) { + if (force != null) { + continuousForce.set(force); + } + if (location != null) { + continuousForceLocation.set(location); + } + applyForce = apply; + + } + + /** + * Use to enable/disable continuous force + * @param apply set to false to disable + */ + public synchronized void applyContinuousForce(boolean apply) { + applyForce = apply; + } + + /** + * Get the currently applied continuous torque + * @return null if no torque is applied + */ + public synchronized Vector3f getContinuousTorque() { + if (applyTorque) { + return continuousTorque; + } else { + return null; + } + } + + /** + * Get the currently applied continuous torque + * @param vec the vector to store the continuous torque in + * @return null if no torque is applied + */ + public synchronized Vector3f getContinuousTorque(Vector3f vec) { + if (applyTorque) { + return vec.set(continuousTorque); + } else { + return null; + } + } + + /** + * Apply a continuous torque to this PhysicsNode. The torque is updated automatically each + * tick so you only need to set it once and then set it to false to stop applying + * the torque. + * @param apply true if the force should be applied each physics tick + * @param vec the vector of the force to apply + */ + public synchronized void applyContinuousTorque(boolean apply, Vector3f vec) { + if (vec != null) { + continuousTorque.set(vec); + } + applyTorque = apply; + } + + /** + * Use to enable/disable continuous torque + * @param apply set to false to disable + */ + public synchronized void applyContinuousTorque(boolean apply) { + applyTorque = apply; + } + + + + /** + * Apply a force to the PhysicsNode, only applies force if the next physics update call + * updates the physics space.
+ * 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) { + ((PhysicsRigidBody)collisionObject).applyForce(force, location); + } + + /** + * Apply a force to the PhysicsNode, only applies force if the next physics update call + * updates the physics space.
+ * To apply an impulse, use applyImpulse, use applyContinuousForce to apply continuous force. + * + * @param force the force + */ + public void applyCentralForce(final Vector3f force) { + ((PhysicsRigidBody)collisionObject).applyCentralForce(force); + } + + /** + * Apply a force to the PhysicsNode, only applies force if the next physics update call + * updates the physics space.
+ * To apply an impulse, use applyImpulse, use applyContinuousForce to apply continuous force. + * + * @param torque the torque + */ + public void applyTorque(final Vector3f torque) { + ((PhysicsRigidBody)collisionObject).applyTorque(torque); + } + + /** + * Apply an impulse to the PhysicsNode 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) { + ((PhysicsRigidBody)collisionObject).applyImpulse(impulse, rel_pos); + } + + /** + * Apply a torque impulse to the PhysicsNode in the next physics update. + * @param vec + */ + public void applyTorqueImpulse(final Vector3f vec) { + ((PhysicsRigidBody)collisionObject).applyTorqueImpulse(vec); + } + + /** + * Clear all forces from the PhysicsNode + * + */ + public void clearForces() { + ((PhysicsRigidBody)collisionObject).clearForces(); + } + + public void setCollisionShape(CollisionShape collisionShape) { + ((PhysicsRigidBody)collisionObject).setCollisionShape(collisionShape); + } + + /** + * reactivates this PhysicsNode when it has been deactivated because it was not moving + */ + public void activate() { + ((PhysicsRigidBody)collisionObject).activate(); + } + + public boolean isActive() { + return ((PhysicsRigidBody)collisionObject).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) { + ((PhysicsRigidBody)collisionObject).setSleepingThresholds(linear, angular); + } + + public void setLinearSleepingThreshold(float linearSleepingThreshold) { + ((PhysicsRigidBody)collisionObject).setLinearSleepingThreshold(linearSleepingThreshold); + } + + public void setAngularSleepingThreshold(float angularSleepingThreshold) { + ((PhysicsRigidBody)collisionObject).setAngularSleepingThreshold(angularSleepingThreshold); + } + + public float getLinearSleepingThreshold() { + return ((PhysicsRigidBody)collisionObject).getLinearSleepingThreshold(); + } + + public float getAngularSleepingThreshold() { + return ((PhysicsRigidBody)collisionObject).getAngularSleepingThreshold(); + } + + /** + * do not use manually, joints are added automatically + */ + public void addJoint(PhysicsJoint joint) { + ((PhysicsRigidBody)collisionObject).addJoint(joint); + } + + /** + * + */ + public void removeJoint(PhysicsJoint joint) { + ((PhysicsRigidBody)collisionObject).removeJoint(joint); + } + + /** + * Returns a list of connected joints. This list is only filled when + * the PhysicsNode is actually added to the physics space or loaded from disk. + * @return list of active joints connected to this physicsnode + */ + public List getJoints() { + return ((PhysicsRigidBody)collisionObject).getJoints(); + } + + /** + * used internally + */ + public PhysicsRigidBody getRigidBody() { + return ((PhysicsRigidBody)collisionObject); + } + + /** + * destroys this PhysicsNode and removes it from memory + */ + public void destroy() { + ((PhysicsRigidBody)collisionObject).destroy(); + } + + public void attachDebugShape(AssetManager manager) { + collisionObject.attachDebugShape(manager); + } + + public void attachDebugShape(Material mat) { + collisionObject.attachDebugShape(mat); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + } +} diff --git a/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsVehicleNode.java b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsVehicleNode.java new file mode 100644 index 000000000..3b12ec11a --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/nodes/PhysicsVehicleNode.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.nodes; + +import com.jme3.bullet.objects.VehicleWheel; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.VehicleControl; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Node; +import java.io.IOException; + +/** + *

PhysicsVehicleNode - Special PhysicsNode that implements vehicle functions

+ *

+ * From bullet manual:
+ * For most vehicle simulations, it is recommended to use the simplified Bullet + * vehicle model as provided in btRaycast((PhysicsVehicleControl)collisionObject). 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.
+ * 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. + *

+ * @see com.jmex.jbullet.nodes.PhysicsNode + * @see com.jmex.jbullet.PhysicsSpace + * @author normenhansen + * @deprecated in favor of physics Controls + */ +@Deprecated +public class PhysicsVehicleNode extends PhysicsNode { + + public PhysicsVehicleNode() { + } + + public PhysicsVehicleNode(CollisionShape shape) { + collisionObject = new VehicleControl(shape); + addControl(((VehicleControl) collisionObject)); + } + + public PhysicsVehicleNode(Spatial child, CollisionShape shape) { + collisionObject = new VehicleControl(shape); + attachChild(child); + addControl(((VehicleControl) collisionObject)); + } + + public PhysicsVehicleNode(Spatial child, CollisionShape shape, float mass) { + collisionObject = new VehicleControl(shape); + ((VehicleControl) collisionObject).setMass(mass); + attachChild(child); + addControl(((VehicleControl) collisionObject)); + } + + /** + * 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) { + if (spat != null) { + Node wheelNode=new Node("wheelNode"); + wheelNode.attachChild(spat); + attachChild(wheelNode); + return ((VehicleControl) collisionObject).addWheel(wheelNode, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); + } + return ((VehicleControl) collisionObject).addWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); + } + + /** + * This rebuilds the vehicle as there is no way in bullet to remove a wheel. + * @param wheel + */ + public void removeWheel(int wheel) { + ((VehicleControl) collisionObject).removeWheel(wheel); + } + + /** + * 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 ((VehicleControl) collisionObject).getWheel(wheel); + } + + /** + * @return the frictionSlip + */ + public float getFrictionSlip() { + return ((VehicleControl) collisionObject).getFrictionSlip(); + } + + /** + * Use before adding wheels, this is the default used when adding wheels. + * After adding the wheel, use direct wheel access.
+ * 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) { + ((VehicleControl) collisionObject).setFrictionSlip(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) { + ((VehicleControl) collisionObject).setFrictionSlip(wheel, 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) { + ((VehicleControl) collisionObject).setRollInfluence(wheel, rollInfluence); + } + + /** + * @return the maxSuspensionTravelCm + */ + public float getMaxSuspensionTravelCm() { + return ((VehicleControl) collisionObject).getMaxSuspensionTravelCm(); + } + + /** + * Use before adding wheels, this is the default used when adding wheels. + * After adding the wheel, use direct wheel access.
+ * The maximum distance the suspension can be compressed (centimetres) + * @param maxSuspensionTravelCm the maxSuspensionTravelCm to set + */ + public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) { + ((VehicleControl) collisionObject).setMaxSuspensionTravelCm(maxSuspensionTravelCm); + } + + /** + * The maximum distance the suspension can be compressed (centimetres) + * @param wheel + * @param maxSuspensionTravelCm + */ + public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) { + ((VehicleControl) collisionObject).setMaxSuspensionForce(wheel, maxSuspensionTravelCm); + } + + public float getMaxSuspensionForce() { + return ((VehicleControl) collisionObject).getMaxSuspensionForce(); + } + + /** + * 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) { + ((VehicleControl) collisionObject).setMaxSuspensionForce(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) { + ((VehicleControl) collisionObject).setMaxSuspensionForce(wheel, maxSuspensionForce); + } + + /** + * @return the suspensionCompression + */ + public float getSuspensionCompression() { + return ((VehicleControl) collisionObject).getSuspensionCompression(); + } + + /** + * Use before adding wheels, this is the default used when adding wheels. + * After adding the wheel, use direct wheel access.
+ * 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.
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping
+ * 0.1 to 0.3 are good values + * @param suspensionCompression the suspensionCompression to set + */ + public void setSuspensionCompression(float suspensionCompression) { + ((VehicleControl) collisionObject).setSuspensionCompression(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.
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping
+ * 0.1 to 0.3 are good values + * @param wheel + * @param suspensionCompression + */ + public void setSuspensionCompression(int wheel, float suspensionCompression) { + ((VehicleControl) collisionObject).setSuspensionCompression(wheel, suspensionCompression); + } + + /** + * @return the suspensionDamping + */ + public float getSuspensionDamping() { + return ((VehicleControl) collisionObject).getSuspensionDamping(); + } + + /** + * Use before adding wheels, this is the default used when adding wheels. + * After adding the wheel, use direct wheel access.
+ * 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) { + ((VehicleControl) collisionObject).setSuspensionDamping(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) { + ((VehicleControl) collisionObject).setSuspensionDamping(wheel, suspensionDamping); + } + + /** + * @return the suspensionStiffness + */ + public float getSuspensionStiffness() { + return ((VehicleControl) collisionObject).getSuspensionStiffness(); + } + + /** + * Use before adding wheels, this is the default used when adding wheels. + * After adding the wheel, use direct wheel access.
+ * 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) { + ((VehicleControl) collisionObject).setSuspensionStiffness(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) { + ((VehicleControl) collisionObject).setSuspensionStiffness(wheel, suspensionStiffness); + } + + /** + * Reset the suspension + */ + public void resetSuspension() { + ((VehicleControl) collisionObject).resetSuspension(); + } + + /** + * Apply the given engine force to all wheels, works continuously + * @param force the force + */ + public void accelerate(float force) { + ((VehicleControl) collisionObject).accelerate(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) { + ((VehicleControl) collisionObject).accelerate(wheel, 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) { + ((VehicleControl) collisionObject).steer(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) { + ((VehicleControl) collisionObject).steer(wheel, value); + } + + /** + * Apply the given brake force to all wheels, works continuously + * @param force the force + */ + public void brake(float force) { + ((VehicleControl) collisionObject).brake(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) { + ((VehicleControl) collisionObject).brake(wheel, force); + } + + /** + * Get the current speed of the vehicle in km/h + * @return + */ + public float getCurrentVehicleSpeedKmHour() { + return ((VehicleControl) collisionObject).getCurrentVehicleSpeedKmHour(); + } + + /** + * Get the current forward vector of the vehicle in world coordinates + * @param vector + * @return + */ + public Vector3f getForwardVector(Vector3f vector) { + return ((VehicleControl) collisionObject).getForwardVector(vector); + } + + /** + * used internally + */ + public PhysicsVehicle getVehicle() { + return ((VehicleControl) collisionObject); + } + + public void destroy() { + ((VehicleControl) collisionObject).destroy(); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + } +} 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 000000000..0cd38b546 --- /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.math.Vector3f; +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 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 = fallSpeed; + 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 000000000..a61b791b6 --- /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.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.scene.Spatial; +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.Vector3f; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * From Bullet manual:
+ * 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.
+ * @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 overlappingObjects = new LinkedList(); + + 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 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 000000000..7fae1a339 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsRigidBody.java @@ -0,0 +1,697 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +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.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.debug.Arrow; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + *

PhysicsRigidBody - Basic physics object

+ * @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 joints = new ArrayList(); + + 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 (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 + * @param location the location of the actual physics object is stored in this Vector3f + */ + public Vector3f getPhysicsLocation() { + return getPhysicsLocation(null); + } + + /** + * Gets the physics object rotation + * @param rotation the rotation of the actual physics object is stored in this Matrix3f + */ + 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 + * @param rotation the rotation of the actual physics object is stored in this Quaternion + */ + 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. 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; + 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
+ * 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 != 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
+ * 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.
+ * 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.
+ * 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.
+ * 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 (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 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 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 000000000..8f5ad6fea --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsVehicle.java @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.DefaultVehicleRaycaster; +import com.bulletphysics.dynamics.vehicle.RaycastVehicle; +import com.bulletphysics.dynamics.vehicle.VehicleRaycaster; +import com.bulletphysics.dynamics.vehicle.VehicleTuning; +import com.bulletphysics.dynamics.vehicle.WheelInfo; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +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.OutputCapsule; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.debug.Arrow; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +/** + *

PhysicsVehicleNode - Special PhysicsNode that implements vehicle functions

+ *

+ * From bullet manual:
+ * 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.
+ * 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. + *

+ * @author normenhansen + */ +public class PhysicsVehicle extends PhysicsRigidBody { + + protected RaycastVehicle vehicle; + protected VehicleTuning tuning; + protected VehicleRaycaster rayCaster; + protected ArrayList wheels = new ArrayList(); + 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.
+ * 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.
+ * 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.
+ * 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.
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping
+ * 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.
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping
+ * 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.
+ * 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.
+ * 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 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()); + 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()); + 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 000000000..458a4f652 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/objects/VehicleWheel.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.bullet.collision.PhysicsCollisionObject; +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.export.Savable; +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.
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping
+ * 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 (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.)
+ * 0.0 = wheels are sliding, 1.0 = wheels have traction. + */ + public float getSkidInfo() { + return wheelInfo.skidInfo; + } + + @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 000000000..7162970b3 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.nodes.PhysicsBaseNode; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.bullet.util.Converter; +import com.jme3.math.Matrix3f; +import com.jme3.scene.Spatial; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * 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 listeners = new LinkedList(); + + 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 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 (spatial instanceof PhysicsBaseNode) { + ((PhysicsBaseNode) spatial).setWorldRotation(worldRotationQuat); + ((PhysicsBaseNode) spatial).setWorldTranslation(worldLocation); + } else 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/CollisionShapeFactory.java b/engine/src/jbullet/com/jme3/bullet/util/CollisionShapeFactory.java new file mode 100644 index 000000000..8fc391f14 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/util/CollisionShapeFactory.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.bullet.util; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.math.Matrix3f; +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.terrain.geomipmap.TerrainQuad; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * + * @author normenhansen, tim8dev + */ +public class CollisionShapeFactory { + + private static CompoundCollisionShape createCompoundShape( + Node rootNode, CompoundCollisionShape shape, boolean meshAccurate, boolean dynamic) { + for (Spatial spatial : rootNode.getChildren()) { + if (spatial instanceof Node) { + createCompoundShape((Node) spatial, shape, meshAccurate, dynamic); + } else if (spatial instanceof Geometry) { + if (meshAccurate) { + CollisionShape childShape = dynamic + ? createSingleDynamicMeshShape((Geometry) spatial) + : createSingleMeshShape((Geometry) spatial); + if (childShape != null) { + shape.addChildShape(childShape, + spatial.getWorldTranslation(), + spatial.getWorldRotation().toRotationMatrix()); + } + } else { + shape.addChildShape(createSingleBoxShape(spatial), + spatial.getWorldTranslation(), + spatial.getWorldRotation().toRotationMatrix()); + } + } + } + return shape; + } + + private static CompoundCollisionShape createCompoundShape( + Node rootNode, CompoundCollisionShape shape, boolean meshAccurate) { + return createCompoundShape(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.
+ * 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.
+ * Objects with "mesh" type collision shape will not collide with each other. + * @return A MeshCollisionShape or a CompoundCollisionShape with MeshCollisionShapes as children if the supplied spatial is a Node. + */ + public static CollisionShape createMeshShape(Spatial spatial) { + if (spatial instanceof TerrainQuad) { + TerrainQuad terrain = (TerrainQuad) spatial; + return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()); + //BELOW: the old way, keeping it here for a little bit as a reference (and so it gets into version control so I can always access it) + /*Map all = new HashMap(); + terrain.getAllTerrainPatchesWithTranslation(all, terrain.getLocalTranslation()); + + Node node = new Node(); + + for (Entry entry : all.entrySet()) { + TerrainPatch tp = entry.getKey(); + Vector3f trans = entry.getValue(); + PhysicsNode n = new PhysicsNode(new HeightfieldCollisionShape(tp.getHeightmap(), trans, tp.getLocalScale()), 0 ); + n.setLocalTranslation(trans); + node.attachChild(n); + }*/ + + } else if (spatial instanceof Geometry) { + return createSingleMeshShape((Geometry) 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.
+ * 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); + } else if (spatial instanceof Node) { + return createCompoundShape((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); + } 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.
+ * Objects with "mesh" type collision shape will not collide with each other. + */ + public static MeshCollisionShape createSingleMeshShape(Geometry geom) { + Mesh mesh = geom.getMesh(); + if (mesh != null) { + MeshCollisionShape mColl = new MeshCollisionShape(mesh); + mColl.setScale(geom.getWorldScale()); + 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 + */ + public static BoxCollisionShape createSingleBoxShape(Spatial spatial) { + spatial.setModelBound(new BoundingBox()); + BoxCollisionShape shape = new BoxCollisionShape( + ((BoundingBox) spatial.getWorldBound()).getExtent(new Vector3f())); + return shape; + } + + /** + * This method creates a hull collision shape for the given mesh.
+ */ + public static HullCollisionShape createSingleDynamicMeshShape(Geometry geom) { + Mesh mesh = geom.getMesh(); + if (mesh != null) { + HullCollisionShape dynamicShape = new HullCollisionShape(mesh); + dynamicShape.setScale(geom.getWorldScale()); + 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 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/jbullet/com/jme3/bullet/util/Converter.java b/engine/src/jbullet/com/jme3/bullet/util/Converter.java new file mode 100644 index 000000000..e65cb6c26 --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/util/Converter.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.IndexBuffer; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +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.getIndexBuffer(); + 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)); + } + + 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.getFloatBuffer(Type.Position).clear(); + jmeMesh.updateCounts(); + jmeMesh.updateBound(); + + 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 000000000..021d9f79e --- /dev/null +++ b/engine/src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.TempVars; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +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.
+ * To attach a debug shape to a physics object, call attachDebugShape(AssetManager manager); 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 children = shape.getChildren(); + Node node = new Node("DebugShapeNode"); + for (Iterator 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(); + assert vars.lock(); + Matrix3f tempRot = vars.tempMat3; + + tempRot.set(geometry.getLocalRotation()); + childCollisionShape.rotation.mult(tempRot, tempRot); + geometry.setLocalRotation(tempRot); + + assert vars.unlock(); + + 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())); + } + else if(shape.getCShape() instanceof ConcaveShape) + { + mesh=new Mesh(); + mesh.setBuffer(Type.Position, 3, getVertices((ConcaveShape)shape.getCShape())); + } + 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; + final int byteBufferSize = numberOfFloats * Float.SIZE; + FloatBuffer vertices = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer(); + + // 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 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. + *

+ * 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 vertices; + + public BufferedTriangleCallback() { + vertices = new ArrayList(); + } + + @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; + final int byteBufferSize = numberOfFloats * Float.SIZE; + FloatBuffer verticesBuffer = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer(); + + // 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/jheora/com/jme3/video/Clock.java b/engine/src/jheora/com/jme3/video/Clock.java new file mode 100644 index 000000000..382b1c8a8 --- /dev/null +++ b/engine/src/jheora/com/jme3/video/Clock.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.video; + +public interface Clock { + + public static final long MILLIS_TO_NANOS = 1000000; + public static final long SECONDS_TO_NANOS = 1000000000; + + public long getTime(); + public double getTimeSeconds(); +} diff --git a/engine/src/jheora/com/jme3/video/RingBuffer.java b/engine/src/jheora/com/jme3/video/RingBuffer.java new file mode 100644 index 000000000..2fecaaf52 --- /dev/null +++ b/engine/src/jheora/com/jme3/video/RingBuffer.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.video; + +public final class RingBuffer { + + private final int bufSize; + private final byte[] buf; + private final Object lock = new Object(); + + private volatile int writePos; + private volatile int readPos; + +// private volatile boolean eof; + private volatile long totalRead = 0, + totalWritten = 0; + + public RingBuffer(int size) { + bufSize = size; + buf = new byte[size]; + } + + public int remainingForWrite() { + if (writePos < readPos) return readPos - writePos - 1; + if (writePos == readPos) return bufSize - 1; + return bufSize - (writePos - readPos) - 1; + } + + public void write(byte[] data, int offset, int length) { + if (length == 0) return; + + synchronized (lock) { + while (remainingForWrite() < length) { + System.out.println("Warning: Audio decoder starved, waiting for player"); + try { + lock.wait(); + } catch (InterruptedException ex) { + } + } + + // copy data + if (writePos >= readPos) { + int l = Math.min(length, bufSize - writePos); + System.arraycopy(data, offset, buf, writePos, l); + writePos += l; + if (writePos >= bufSize) writePos = 0; + if (length > l) write(data, offset + l, length - l); + } else { + int l = Math.min(length, readPos - writePos - 1); + System.arraycopy(data, offset, buf, writePos, l); + writePos += l; + if (writePos >= bufSize) writePos = 0; + } + + totalWritten += length; + } + } + + public int remainingForRead() { + if (writePos < readPos) return bufSize - (readPos - writePos); + if (writePos == readPos) return 0; + return writePos - readPos; + } + + public int skip(int amount){ + if (amount <= 0) return 0; + int dataLen = 0; + + synchronized (lock){ + if (remainingForRead() <= 0) + return 0; + + amount = Math.min(amount, remainingForRead()); + + // copy data + if (readPos < writePos) { + int l = Math.min(amount, writePos - readPos); + readPos += l; + if (readPos >= bufSize) readPos = 0; + dataLen = l; + } else { + int l = Math.min(amount, bufSize - readPos); + readPos += l; + if (readPos >= bufSize) readPos = 0; + dataLen = l; + if (amount > l) dataLen += skip(amount - l); + } + lock.notifyAll(); + } + + totalRead += dataLen; + return dataLen; + } + + public int read(byte[] data, int offset, int len) { + if (len == 0) return 0; + int dataLen = 0; + + synchronized (lock) { + // see if we have enough data + if (remainingForRead() <= 0){ + System.out.println("Warning: Audio starved. Not enough data."); + return 0; + } + + len = Math.min(len, remainingForRead()); + + // copy data + if (readPos < writePos) { + int l = Math.min(len, writePos - readPos); + System.arraycopy(buf, readPos, data, offset, l); + readPos += l; + if (readPos >= bufSize) readPos = 0; + dataLen = l; + } else { + int l = Math.min(len, bufSize - readPos); + System.arraycopy(buf, readPos, data, offset, l); + readPos += l; + if (readPos >= bufSize) readPos = 0; + dataLen = l; + if (len > l) dataLen += read(data, offset + l, len - l); + } + lock.notifyAll(); + } + + totalRead += dataLen; + return dataLen; + } + + public long getTotalRead(){ + return totalRead; + } + + public long getTotalWritten(){ + return totalWritten; + } + +// public boolean isEOF() { +// return eof; +// } +// +// public void setEOF(boolean eof) { +// this.eof = eof; +// } + +} diff --git a/engine/src/jheora/com/jme3/video/SystemClock.java b/engine/src/jheora/com/jme3/video/SystemClock.java new file mode 100644 index 000000000..f9e6bd454 --- /dev/null +++ b/engine/src/jheora/com/jme3/video/SystemClock.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.video; + +public class SystemClock implements Clock { + + private long startTime = 0; + + public SystemClock(){ + } + + public boolean needReset(){ + return startTime == 0; + } + + public void reset(){ + startTime = System.nanoTime(); + } + + public long getTime() { + return System.nanoTime() - startTime; + } + + public double getTimeSeconds(){ + return (double) getTime() / Clock.SECONDS_TO_NANOS; + } + +} diff --git a/engine/src/jheora/com/jme3/video/TestVideoPlayer.java b/engine/src/jheora/com/jme3/video/TestVideoPlayer.java new file mode 100644 index 000000000..87690da19 --- /dev/null +++ b/engine/src/jheora/com/jme3/video/TestVideoPlayer.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.video; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.system.AppSettings; +import com.jme3.texture.Image; +import com.jme3.ui.Picture; +import com.jme3.video.plugins.jheora.AVThread; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class TestVideoPlayer extends SimpleApplication { + + private Picture picture; + private AVThread decoder; + private Thread videoThread; + private VQueue videoQueue; + + private long lastFrameTime = 0; + private Clock masterClock; + private AudioNode source; + + private float waitTime = 0; + private VFrame frameToDraw = null; + + public static void main(String[] args){ + TestVideoPlayer app = new TestVideoPlayer(); + AppSettings settings = new AppSettings(true); + settings.setFrameRate(60); + app.setSettings(settings); + app.start(); + } + + private void createVideo(){ + try { + // uncomment to play video from harddrive +// FileInputStream fis = new FileInputStream("E:\\bunny.ogg"); + InputStream fis = new URL("http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.ogg").openStream(); + + // increasing queued frames value from 5 will make web streamed + // playback smoother at the cost of memory. + videoQueue = new VQueue(5); + decoder = new AVThread(fis, videoQueue); + videoThread = new Thread(decoder, "Jheora Video Decoder"); + videoThread.setDaemon(true); + videoThread.start(); + masterClock = decoder.getMasterClock(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public void simpleInitApp() { + picture = new Picture("VideoPicture", true); + picture.setPosition(0, 0); + picture.setWidth(settings.getWidth()); + picture.setHeight(settings.getHeight()); + picture.setImage(assetManager, "Interface/Logo/Monkey.jpg", false); + + // attach geometry to orthoNode + rootNode.attachChild(picture); + + // start video playback + createVideo(); + } + + private void drawFrame(VFrame frame){ + Image image = frame.getImage(); + frame.setImage(image); + picture.setTexture(assetManager, frame, false); + + // note: this forces renderer to upload frame immediately, + // since it is going to be returned to the video queue pool + // it could be used again. + renderer.setTexture(0, frame); + videoQueue.returnFrame(frame); + lastFrameTime = frame.getTime(); + } + + private void waitNanos(long time){ + long millis = (long) (time / Clock.MILLIS_TO_NANOS); + int nanos = (int) (time - (millis * Clock.MILLIS_TO_NANOS)); + + try { + Thread.sleep(millis, nanos); + }catch (InterruptedException ex){ + stop(); + return; + } + } + + @Override + public void simpleUpdate(float tpf){ + if (source == null){ + if (decoder.getAudioStream() != null){ + source = new AudioNode(decoder.getAudioStream(), null); + source.setPositional(false); + source.setReverbEnabled(false); + audioRenderer.playSource(source); + }else{ + // uncomment this statement to be able to play videos + // without audio. + return; + } + } + + if (waitTime > 0){ + waitTime -= tpf; + if (waitTime > 0) + return; + else{ + waitTime = 0; + drawFrame(frameToDraw); + frameToDraw = null; + } + }else{ + VFrame frame; + try { + frame = videoQueue.take(); + } catch (InterruptedException ex){ + stop(); + return; + } + if (frame.getTime() < lastFrameTime){ + videoQueue.returnFrame(frame); + return; + } + + if (frame.getTime() == -2){ + // end of stream + System.out.println("End of stream"); + stop(); + return; + } + + long AV_SYNC_THRESHOLD = 1 * Clock.MILLIS_TO_NANOS; + + long delay = frame.getTime() - lastFrameTime; +// delay -= tpf * Clock.SECONDS_TO_NANOS; + long diff = frame.getTime() - masterClock.getTime(); + long syncThresh = delay > AV_SYNC_THRESHOLD ? delay : AV_SYNC_THRESHOLD; + + // if difference is more than 1 second, synchronize. + if (Math.abs(diff) < Clock.SECONDS_TO_NANOS){ + if(diff <= -syncThresh) { + delay = 0; + } else if(diff >= syncThresh) { + delay = 2 * delay; + } + } +// delay = diff; + + System.out.println("M: "+decoder.getSystemClock().getTimeSeconds()+ + ", V: "+decoder.getVideoClock().getTimeSeconds()+ + ", A: "+decoder.getAudioClock().getTimeSeconds()); + + if (delay > 0){ + waitNanos(delay); + drawFrame(frame); +// waitTime = (float) ((double) delay / Clock.SECONDS_TO_NANOS); +// frameToDraw = frame; + }else{ + videoQueue.returnFrame(frame); + lastFrameTime = frame.getTime(); + } + } + } + +} diff --git a/engine/src/jheora/com/jme3/video/VFrame.java b/engine/src/jheora/com/jme3/video/VFrame.java new file mode 100644 index 000000000..e05ca722a --- /dev/null +++ b/engine/src/jheora/com/jme3/video/VFrame.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.video; + +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; + +public class VFrame extends Texture2D { + + private long time; + + public VFrame(int width, int height){ + super(width, height, Format.RGBA8); + getImage().setData(BufferUtils.createByteBuffer(width*height*4)); + } + + public long getTime(){ + return time; + } + + public void setTime(long time){ + this.time = time; + } + + @Override + public String toString(){ + return super.toString() + "[Time="+time+"]"; + } + +} diff --git a/engine/src/jheora/com/jme3/video/VQueue.java b/engine/src/jheora/com/jme3/video/VQueue.java new file mode 100644 index 000000000..47c74654f --- /dev/null +++ b/engine/src/jheora/com/jme3/video/VQueue.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.video; + +import java.util.ArrayList; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class VQueue extends ArrayBlockingQueue { + +// private final ArrayList returnedFrames; + private final ArrayBlockingQueue returnedFrames; + + public VQueue(int bufferedFrames){ + super(bufferedFrames); +// returnedFrames = new ArrayList(remainingCapacity()); + returnedFrames = new ArrayBlockingQueue(bufferedFrames * 3); + } + + public VFrame nextReturnedFrame(boolean waitForIt){ + // synchronized (returnedFrames){ + // while (returnedFrames.size() == 0){ + // if (!waitForIt) + // return null; + // + // try { + // returnedFrames.wait(); + // } catch (InterruptedException ex) { + // } + // } + // } + // } + + try { + return returnedFrames.take(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return null; + } + } + + public void returnFrame(VFrame frame){ + returnedFrames.add(frame); + +// synchronized (returnedFrames){ +// returnedFrames.add(frame); +// returnedFrames.notifyAll(); +// } + } +} diff --git a/engine/src/jheora/com/jme3/video/plugins/jheora/ADecoder.java b/engine/src/jheora/com/jme3/video/plugins/jheora/ADecoder.java new file mode 100644 index 000000000..029b5fc66 --- /dev/null +++ b/engine/src/jheora/com/jme3/video/plugins/jheora/ADecoder.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.video.plugins.jheora; + +import com.jcraft.jogg.Packet; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Info; +import com.jme3.audio.AudioStream; +import com.jme3.video.Clock; +import com.jme3.video.RingBuffer; +import com.jme3.video.SystemClock; +import java.io.InputStream; + +public class ADecoder extends InputStream implements Clock { + + private int packetIndex = 0; + + private final DspState dsp; + private Block block; + + private Info info; + private Comment comment; + + private float[][][] pcmAll = new float[1][][]; + private int[] index; + + private AudioStream stream; + private RingBuffer ringBuffer = new RingBuffer(48000 * 2 * 2 * 2); + + private int UNCOMP_BUFSIZE = 4096; + private byte[] uncompBuf = new byte[UNCOMP_BUFSIZE]; + + private long lastPts = 0; + private long lastWritten = 0; + private long lastRead = 0; + private long lastPtsRead = 0; + private long lastPtsWrite = 0; + + private long timeDiffSum = 0; + private int timesDesynced = 0; + private Clock masterClock; + + public ADecoder(){ + info = new Info(); + info.init(); + + comment = new Comment(); + comment.init(); + + dsp = new DspState(); + block = new Block(dsp); + } + + public void setMasterClock(Clock masterClock) { + this.masterClock = masterClock; + } + + public AudioStream getAudioStream(){ + return stream; + } + + public long getTime(){ + long bytesRead = ringBuffer.getTotalRead(); + if (bytesRead == 0) + return 0; + + //long diff = bytesRead - lastWritten; + long diff = bytesRead; + long diffNs = (diff * Clock.SECONDS_TO_NANOS) / (2 * info.channels * info.rate); + long timeSinceLastRead = System.nanoTime() - lastPtsRead; + return /*lastPts +*/ diffNs + timeSinceLastRead; + } + + public double getTimeSeconds(){ + return (double) getTime() / Clock.SECONDS_TO_NANOS; + } + + public int read(){ + byte[] buf = new byte[1]; + int read = read(buf, 0, 1); + if (read < 0) + return -1; + else + return buf[0] & 0xff; + } + + private static final long NOSYNC_THRESH = 3 * Clock.SECONDS_TO_NANOS, + AUDIO_DIFF_THRESH = Clock.SECONDS_TO_NANOS / 2; + + private static final int NUM_DIFFS_FOR_SYNC = 5; + private static final double PRODUCT_FOR_PREV = 6.0 / 10.0, + PRODUCT_FOR_PREV_INV = 4.0 / 10.0; + + private int needToSkip = 0; + + /** + * Only useful if audio is synced to something else. + */ + private void sync(){ + if (needToSkip > 0){ + int skipped = ringBuffer.skip(needToSkip); + System.out.println("Skipped: "+skipped); + needToSkip -= skipped; + } + + long masterTime = masterClock.getTime(); + long audioTime = getTime(); + long diff = audioTime - masterTime; + if (diff < NOSYNC_THRESH){ + timeDiffSum = diff + (long) (timeDiffSum * PRODUCT_FOR_PREV); + if (timesDesynced < NUM_DIFFS_FOR_SYNC){ + timesDesynced ++; + }else{ + long avgDiff = (long) (timeDiffSum * PRODUCT_FOR_PREV_INV); + if (Math.abs(avgDiff) >= AUDIO_DIFF_THRESH){ + if (diff < 0){ + int toSkip = (int) ((-diff * 2 * info.channels * info.rate) / Clock.SECONDS_TO_NANOS); + int skipped = ringBuffer.skip(toSkip); + System.out.println("Skipped: "+skipped); + if (skipped < toSkip) + needToSkip = toSkip - skipped; + + timeDiffSum = 0; + timesDesynced = 0; + } + } + } + }else{ + timesDesynced = 0; + timeDiffSum = 0; + } + } + + @Override + public int read(byte[] buf, int offset, int length){ +// int diff = (int) (ringBuffer.getTotalWritten() - ringBuffer.getTotalRead()); +// if ( diff > info.rate * info.channels * 2){ +// System.out.println("Warning: more than 1 second lag for audio. Adjusting.."); +// ringBuffer.skip( diff ); +// } + + +// if (masterClock != null) +// sync(); + + int r = ringBuffer.read(buf, offset, length); + if (r <= 0){ +// // put silence + for (int i = 0; i < length; i++){ + buf[offset + i] = 0x0; + } + return length; + }else{ + lastPtsRead = System.nanoTime(); + } + return r; + } + + public void decodeDsp(Packet packet){ + if (block.synthesis(packet) == 0) { + dsp.synthesis_blockin(block); + } + + int samplesAvail; + int channels = info.channels; + while ((samplesAvail = dsp.synthesis_pcmout(pcmAll, index)) > 0) { + float[][] pcm = pcmAll[0]; + int samplesCanRead = UNCOMP_BUFSIZE / (channels*2); + int samplesToRead = (samplesAvail < samplesCanRead ? samplesAvail : samplesCanRead); + + // convert floats to 16 bit signed ints and interleave + for (int i = 0; i < channels; i++) { + // each sample is two bytes, the sample for the 2nd + // channel is at index 2, etc. + int writeOff = i * 2; + int readOff = index[i]; + for (int j = 0; j < samplesToRead; j++) { + int val = (int) (pcm[i][readOff + j] * 32767.0); + // guard against clipping + if (val > 32767) { + val = 32767; + }if (val < -32768) { + val = -32768; + } + uncompBuf[writeOff] = (byte) (val); + uncompBuf[writeOff + 1] = (byte) (val >> 8); + + writeOff += 2 * channels; // each sample is 2 bytes + } + } + + ringBuffer.write(uncompBuf, 0, samplesToRead * channels * 2); + + // tell vorbis how many samples were actualy consumed + dsp.synthesis_read(samplesToRead); + } + } + + @Override + public void close(){ + } + + public void decode(Packet packet){ + if (packetIndex < 3) { + if (info.synthesis_headerin(comment, packet) < 0) { + // error case; not a Vorbis header + System.err.println("does not contain Vorbis audio data."); + return; + } + if (packetIndex == 2) { + dsp.synthesis_init(info); + block.init(dsp); + System.out.println("vorbis: "+info); + System.out.println(comment.toString()); + index = new int[info.channels]; + + if (stream == null){ + stream = new AudioStream(); + stream.setupFormat(info.channels, 16, info.rate); + stream.updateData(this, -1); + } + + if (masterClock instanceof SystemClock){ + SystemClock clock = (SystemClock) masterClock; + if (clock.needReset()){ + clock.reset(); + System.out.println("Note: master clock was reset by audio"); + } + } + } + } else { + long gp = packet.granulepos; + if (gp != -1){ + lastPts = (gp * Clock.SECONDS_TO_NANOS) / info.rate; + lastWritten = ringBuffer.getTotalWritten(); + lastRead = ringBuffer.getTotalRead(); + lastPtsWrite = System.nanoTime(); + } + + decodeDsp(packet); + } + packetIndex++; + } + +} diff --git a/engine/src/jheora/com/jme3/video/plugins/jheora/AVThread.java b/engine/src/jheora/com/jme3/video/plugins/jheora/AVThread.java new file mode 100644 index 000000000..c481c383b --- /dev/null +++ b/engine/src/jheora/com/jme3/video/plugins/jheora/AVThread.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.video.plugins.jheora; + +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jme3.audio.AudioStream; +import com.jme3.util.IntMap; +import com.jme3.video.Clock; +import com.jme3.video.SystemClock; +import com.jme3.video.VQueue; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class AVThread implements Runnable { + + private static final Logger logger = Logger.getLogger(AVThread.class.getName()); + + private static final int BUFFSIZE = 8192; + + /** + * OggPage. + */ + private Page page; + + /** + * OggPacket, constructed from OggPages. + */ + private Packet packet; + + /** + * SyncState. + */ + private SyncState syncState; + + /** + * Stream of .ogg file. + */ + private InputStream oggStream; + + private AtomicBoolean cancel = new AtomicBoolean(false); + + private IntMap streams = new IntMap(); + private int theoraSerial, vorbisSerial; + private ADecoder audioDecoder; + private VDecoder videoDecoder; + private Clock masterClock; + private Clock systemClock; + + public AVThread(InputStream oggStream, VQueue videoQueue){ + this.oggStream = oggStream; + videoDecoder = new VDecoder(videoQueue); + audioDecoder = new ADecoder(); + systemClock = new SystemClock(); +// masterClock = systemClock;//audioDecoder; + masterClock = audioDecoder; + audioDecoder.setMasterClock(masterClock); + videoDecoder.setMasterClock(masterClock); +// masterClock = videoDecoder; +// masterClock = systemClock; +// audioDecoder.setMasterClock(masterClock); + } + + public AudioStream getAudioStream(){ + return audioDecoder.getAudioStream(); + } + + public void stop(){ + cancel.set(true); + } + + private void done(){ + videoDecoder.close(); + audioDecoder.close(); + try { + oggStream.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error while closing Ogg Video", ex); + } + } + + public Clock getMasterClock(){ + return masterClock; + } + + public Clock getSystemClock(){ + return systemClock; + } + + public Clock getVideoClock(){ + return videoDecoder; + } + + public Clock getAudioClock(){ + return audioDecoder; + } + + public void run(){ + page = new Page(); + packet = new Packet(); + syncState = new SyncState(); + + while (!cancel.get()) { + int index = syncState.buffer(BUFFSIZE); + + // Read from stream into syncState's buffer. + int read; + try { + read = oggStream.read(syncState.data, index, BUFFSIZE); + } catch (IOException ex){ + logger.log(Level.SEVERE, "Error while decoding Ogg Video", ex); + return; + } + + if (read < 0){ + // EOF + break; + } + + syncState.wrote(read); + + while (!cancel.get()) { + // Acquire page from syncState + int res = syncState.pageout(page); + if (res == 0) + break; // need more data + + if (res == -1) { + // missing or corrupt data at this page position + // no reason to complain; already complained above + } else { + int serial = page.serialno(); + StreamState state = streams.get(serial); + boolean newStream = false; + if (state == null){ + state = new StreamState(); + state.init(serial); + state.reset(); + streams.put(serial, state); + newStream = true; + } + + // Give StreamState the page + res = state.pagein(page); + if (res < 0) { + // error; stream version mismatch perhaps + System.err.println("Error reading first page of Ogg bitstream data."); + return; + } + while (!cancel.get()) { + // Get a packet out of the stream state + res = state.packetout(packet); + if (res == 0) + break; + + if (res == -1) { + // missing or corrupt data at this page position + // no reason to complain; already complained above + } else { + // Packet acquired! + if (newStream) { + // typefind + int packetId = packet.packet; + byte[] packetBase = packet.packet_base; + if (packetBase[packetId + 1] == 0x76) { + vorbisSerial = serial; + } else if (packet.packet_base[packet.packet + 1] == 0x73) { + // smoke video! ignored + logger.log(Level.WARNING, "Smoke video detected. Unsupported!"); + } else if (packet.packet_base[packet.packet + 1] == 0x74) { + theoraSerial = serial; + } + } + if (serial == theoraSerial){ + videoDecoder.decode(packet); + }else if (serial == vorbisSerial){ + audioDecoder.decode(packet); + } + } + } + } + } + } + done(); + } + +} diff --git a/engine/src/jheora/com/jme3/video/plugins/jheora/VDecoder.java b/engine/src/jheora/com/jme3/video/plugins/jheora/VDecoder.java new file mode 100644 index 000000000..916ff74fe --- /dev/null +++ b/engine/src/jheora/com/jme3/video/plugins/jheora/VDecoder.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.video.plugins.jheora; + +import com.fluendo.jheora.Comment; +import com.fluendo.jheora.Info; +import com.fluendo.jheora.State; +import com.fluendo.jheora.YUVBuffer; +import com.jcraft.jogg.Packet; +import com.jme3.video.Clock; +import com.jme3.video.SystemClock; +import com.jme3.video.VFrame; +import com.jme3.video.VQueue; +import java.nio.ByteBuffer; + +public class VDecoder implements Clock { + + private int packetIndex = 0; + + private Info info; + private Comment comment; + + private State state; + private YUVBuffer yuv; + + private int xOff, yOff, width, height; + private YUVConv conv = new YUVConv(); + + private VQueue videoQueue; + + private long lastTs = -1; + private long lastUpdateTime = 0; + private Clock masterClock; + + public VDecoder(VQueue queue) { + info = new Info(); + comment = new Comment(); + state = new State(); + yuv = new YUVBuffer(); + videoQueue = queue; + } + + public void setMasterClock(Clock masterClock) { + this.masterClock = masterClock; + } + + public long getTime(){ + if (lastTs == -1) + return 0; + + long timeDiff = System.nanoTime() - lastUpdateTime; + return lastTs + timeDiff; + } + + public double getTimeSeconds(){ + return (double) getTime() / Clock.SECONDS_TO_NANOS; + } + + private void initializeFrames(){ + for (int i = 0; i < videoQueue.remainingCapacity(); i++){ + videoQueue.returnFrame(new VFrame(width, height)); + } + } + + private void decodeRgbFromBuffer(long time){ + VFrame frame = videoQueue.nextReturnedFrame(true); + conv.convert(yuv, xOff, yOff, width, height); + int[] rgb = conv.getRGBData(); + + frame.setTime(time); + ByteBuffer data = frame.getImage().getData(0); + data.clear(); + data.asIntBuffer().put(rgb); + + try { + // if it throws an exception someone + // else modified it. "unknown error".. + videoQueue.put(frame); + } catch (InterruptedException ex) { + } + } + + public void close(){ + // enqueue a frame with time == -2 to indicate end of stream + VFrame frame = videoQueue.nextReturnedFrame(true); + frame.setTime(-2); + try { + videoQueue.put(frame); + } catch (InterruptedException ex) { + } + } + + public void decode(Packet packet) { + //System.out.println ("creating packet"); + if (packetIndex < 3) { + //System.out.println ("decoding header"); + if (info.decodeHeader(comment, packet) < 0) { + // error case; not a theora header + System.err.println("does not contain Theora video data."); + return; + } + if (packetIndex == 2) { + state.decodeInit(info); + + System.out.println("theora frame: " + info.frame_width + "x" + info.frame_height); + System.out.println("theora resolution: " + info.width + "x" + info.height); + System.out.println("theora aspect: " + info.aspect_numerator + "x" + info.aspect_denominator); + System.out.println("theora framerate: " + info.fps_numerator + "x" + info.fps_denominator); + + xOff = info.offset_x; + yOff = info.offset_y; + width = info.frame_width; + height = info.frame_height; + initializeFrames(); + + if (masterClock instanceof SystemClock){ + SystemClock clock = (SystemClock) masterClock; + if (clock.needReset()){ + clock.reset(); + System.out.println("Note: master clock was reset by video"); + } + } + } + } else { + // convert to nanos + long granulePos = packet.granulepos; + long time = (long) (state.granuleTime(granulePos) * Clock.SECONDS_TO_NANOS); + long oneFrameTime = (long) ((Clock.SECONDS_TO_NANOS * info.fps_denominator) / info.fps_numerator); + if (time >= 0){ + lastTs = time; + }else{ + lastTs += oneFrameTime; + time = lastTs; + } + lastUpdateTime = System.nanoTime(); + + if (state.decodePacketin(packet) != 0) { + System.err.println("Error Decoding Theora."); + return; + } + +// if (time >= 0){ + if (state.decodeYUVout(yuv) != 0) { + System.err.println("Error getting the picture."); + return; + } + decodeRgbFromBuffer( time ); +// } + } + packetIndex++; + } + +} diff --git a/engine/src/jheora/com/jme3/video/plugins/jheora/YUVConv.java b/engine/src/jheora/com/jme3/video/plugins/jheora/YUVConv.java new file mode 100644 index 000000000..b6ec9ce2b --- /dev/null +++ b/engine/src/jheora/com/jme3/video/plugins/jheora/YUVConv.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.video.plugins.jheora; + +import com.fluendo.jheora.YUVBuffer; + +public final class YUVConv { + + private int[] pixels; + + private static final int VAL_RANGE = 256; + private static final int SHIFT = 16; + + private static final int CR_FAC = (int) (1.402 * (1 << SHIFT)); + private static final int CB_FAC = (int) (1.772 * (1 << SHIFT)); + private static final int CR_DIFF_FAC = (int) (0.71414 * (1 << SHIFT)); + private static final int CB_DIFF_FAC = (int) (0.34414 * (1 << SHIFT)); + + private static int[] r_tab = new int[VAL_RANGE * 3]; + private static int[] g_tab = new int[VAL_RANGE * 3]; + private static int[] b_tab = new int[VAL_RANGE * 3]; + + static { + setupRgbYuvAccelerators(); + } + + private static final short clamp255(int val) { + val -= 255; + val = -(255 + ((val >> (31)) & val)); + return (short) -((val >> 31) & val); + } + + private static void setupRgbYuvAccelerators() { + for (int i = 0; i < VAL_RANGE * 3; i++) { + r_tab[i] = clamp255(i - VAL_RANGE); + g_tab[i] = clamp255(i - VAL_RANGE) << 8; + b_tab[i] = clamp255(i - VAL_RANGE) << 16; + } + } + + public YUVConv(){ + } + + public int[] getRGBData(){ + return pixels; + } + + public void convert(YUVBuffer yuv, int xOff, int yOff, int width, int height) { + if (pixels == null){ + pixels = new int[width*height]; + } + // Set up starting values for YUV pointers + int YPtr = yuv.y_offset + xOff + yOff * (yuv.y_stride); + int YPtr2 = YPtr + yuv.y_stride; + int UPtr = yuv.u_offset + xOff/2 + (yOff/2)*(yuv.uv_stride); + int VPtr = yuv.v_offset + xOff/2 + (yOff/2)*(yuv.uv_stride); + int RGBPtr = 0; + int RGBPtr2 = width; + int width2 = width / 2; + int height2 = height / 2; + + // Set the line step for the Y and UV planes and YPtr2 + int YStep = yuv.y_stride * 2 - (width2) * 2; + int UVStep = yuv.uv_stride - (width2); + int RGBStep = width; + + for (int i = 0; i < height2; i++) { + for (int j = 0; j < width2; j++) { + // groups of four pixels + int UFactor = yuv.data[UPtr++] - 128; + int VFactor = yuv.data[VPtr++] - 128; + int GFactor = UFactor * CR_DIFF_FAC + VFactor * CB_DIFF_FAC - (VAL_RANGE<>SHIFT] | + b_tab[(YVal + UFactor)>>SHIFT] | + g_tab[(YVal - GFactor)>>SHIFT]; + + YVal = yuv.data[YPtr+1] << SHIFT; + pixels[RGBPtr+1] = r_tab[(YVal + VFactor)>>SHIFT] | + b_tab[(YVal + UFactor)>>SHIFT] | + g_tab[(YVal - GFactor)>>SHIFT]; + + YVal = yuv.data[YPtr2] << SHIFT; + pixels[RGBPtr2] = r_tab[(YVal + VFactor)>>SHIFT] | + b_tab[(YVal + UFactor)>>SHIFT] | + g_tab[(YVal - GFactor)>>SHIFT]; + + YVal = yuv.data[YPtr2+1] << SHIFT; + pixels[RGBPtr2+1] = r_tab[(YVal + VFactor)>>SHIFT] | + b_tab[(YVal + UFactor)>>SHIFT] | + g_tab[(YVal - GFactor)>>SHIFT]; + + YPtr += 2; + YPtr2 += 2; + RGBPtr += 2; + RGBPtr2 += 2; + } + + // Increment the various pointers + YPtr += YStep; + YPtr2 += YStep; + UPtr += UVStep; + VPtr += UVStep; + RGBPtr += RGBStep; + RGBPtr2 += RGBStep; + } + } +} 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 000000000..4c922baca --- /dev/null +++ b/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.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.audio.plugins; + +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.ArrayList; +import java.util.HashMap; +import java.util.Collection; +import java.util.logging.Logger; + +/** + * Implementation of the PhysicalOggStream 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 static final Logger logger = Logger.getLogger(CachedOggStream.class.getName()); + + private boolean closed = false; + private InputStream sourceStream; + private byte[] memoryCache; + private ArrayList pageOffsets = new ArrayList(); + private ArrayList pageLengths = new ArrayList(); + private long cacheLength; + + private boolean bos = false; + private boolean eos = false; + private int pageNumber; + + private HashMap logicalStreams + = new HashMap(); + + /** + * Creates an instance of this class, using the specified file as cache. The + * file is not automatically deleted when this class is disposed. + */ + public CachedOggStream(InputStream stream, int length, int numPages) throws IOException { + logger.info("Creating memory cache of size "+length); + + memoryCache = new byte[length]; + sourceStream = stream; + + while (!eos){ + readOggNextPage(); + } + } + + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + public boolean isOpen() { + return !closed; + } + + public void close() throws IOException { + closed = true; + sourceStream.close(); + } + + public long getCacheLength() { + return cacheLength; + } + + public OggPage getOggPage(int index) throws IOException { + Long offset = (Long) pageOffsets.get(index); + Long length = (Long) pageLengths.get(index); + + byte[] tmpArray = new byte[length.intValue()]; + System.arraycopy(memoryCache, offset.intValue(), tmpArray, 0, length.intValue()); + return OggPage.create(tmpArray); + } + + /** + * Set the current time as granule position + * @param granulePosition + * @throws IOException + */ + public void setTime(long granulePosition) throws IOException { + for (LogicalOggStream los : getLogicalStreams()){ + los.setTime(granulePosition); + } + } + + /** + * Read an OggPage from the input stream and put it in the file's cache. + * @return the page number + * @throws IOException + */ + private int readOggNextPage() throws IOException { + if (eos) // end of stream + return -1; + + // create ogg page for the stream + OggPage op = OggPage.create(sourceStream); + + // find location where to write ogg page + // based on the last ogg page's offset and length. + int listSize = pageOffsets.size(); + long pos = listSize > 0 ? pageOffsets.get(listSize - 1) + pageLengths.get(listSize - 1) : 0; + + // various data in the ogg page that is needed + byte[] arr1 = op.getHeader(); + byte[] arr2 = op.getSegmentTable(); + byte[] arr3 = op.getData(); + + // put in the memory cache + System.arraycopy(arr1, 0, memoryCache, (int) pos, arr1.length); + System.arraycopy(arr2, 0, memoryCache, (int) pos + arr1.length, arr2.length); + System.arraycopy(arr3, 0, memoryCache, (int) pos + arr1.length + arr2.length, arr3.length); + + // append the information of the ogg page into the offset and length lists + pageOffsets.add(pos); + pageLengths.add((long) (arr1.length + arr2.length + arr3.length)); + + // check for beginning of stream + if (op.isBos()){ + bos = true; + } + + // check for end of stream + if (op.isEos()){ + eos = true; + } + + // find the logical ogg stream, if it was created already, based on + // the stream serial + LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber()); + if(los == null) { + // not created, make a new one + los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); + logicalStreams.put(op.getStreamSerialNumber(), los); + los.checkFormat(op); + } + + los.addPageNumberMapping(pageNumber); + los.addGranulePosition(op.getAbsoluteGranulePosition()); + + pageNumber++; + cacheLength = op.getAbsoluteGranulePosition(); + + 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 000000000..1e79b665f --- /dev/null +++ b/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.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 com.jme3.audio.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioStream; +import com.jme3.asset.AssetLoader; +import com.jme3.audio.AudioKey; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +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; + +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; + private 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 < buf.length) { + if ((cnt = vs.readPcm(buf, offset + bytesRead, buf.length - bytesRead)) <= 0) { + endOfStream = true; + break; + } + bytesRead += cnt; + } + + swapBytes(buf, offset, bytesRead); + return bytesRead; + + } + + @Override + public void close() throws IOException{ + vs.close(); + } + + } + + 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); + ByteBuffer data = BufferUtils.createByteBuffer(dataBytes.length); + data.put(dataBytes).flip(); + + vorbisStream.close(); + loStream.close(); + oggStream.close(); + + return data; + } + + private static final 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(){ + return new JOggInputStream(vorbisStream); + } + + public Object load(AssetInfo info) throws IOException { + InputStream in = info.openStream(); + oggStream = new UncachedOggStream(in); + + Collection 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(); + + boolean readStream = ((AudioKey)info.getKey()).isStream(); + + 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()); + audioStream.updateData(readToStream(), -1); + return audioStream; + } + } + +} 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 000000000..1b3ddc292 --- /dev/null +++ b/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.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 com.jme3.audio.plugins; + +import de.jarnbjo.ogg.LogicalOggStream; +import de.jarnbjo.ogg.LogicalOggStreamImpl; +import de.jarnbjo.ogg.OggFormatException; +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.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 pageCache = new LinkedList(); + private HashMap logicalStreams = new HashMap(); + + public UncachedOggStream(InputStream in) throws OggFormatException, IOException { + this.sourceStream = in; + + // read until beginning of stream + while (!bos){ + readNextOggPage(); + } + } + + private void readNextOggPage() throws IOException { + OggPage op = OggPage.create(sourceStream); + if (!op.isBos()){ + bos = true; + } + if (op.isEos()){ + eos = true; + } + + 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 (pageCache.size() == 0){ + readNextOggPage(); + } + + return pageCache.removeFirst(); + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return logicalStreams.get(new Integer(serialNumber)); + } + + public Collection 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/jogl/com/jme3/renderer/jogl/JoglRenderer.java b/engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java new file mode 100644 index 000000000..e1febde85 --- /dev/null +++ b/engine/src/jogl/com/jme3/renderer/jogl/JoglRenderer.java @@ -0,0 +1,1114 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.jogl; + +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GLObjectManager; +import com.jme3.renderer.IDList; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.Renderer; +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 java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.EnumSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.media.opengl.GL; + +public class JoglRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(JoglRenderer.class.getName()); + protected Statistics statistics = new Statistics(); + protected Matrix4f worldMatrix = new Matrix4f(); + protected Matrix4f viewMatrix = new Matrix4f(); + protected Matrix4f projMatrix = new Matrix4f(); + protected FloatBuffer fb16 = BufferUtils.createFloatBuffer(16); + protected IntBuffer ib1 = BufferUtils.createIntBuffer(1); + protected GL gl; + private RenderContext context = new RenderContext(); + private GLObjectManager objManager = new GLObjectManager(); + private EnumSet caps = EnumSet.noneOf(Caps.class); + private boolean powerOf2 = false; + private boolean hardwareMips = false; + private boolean vbo = false; + private int vpX, vpY, vpW, vpH; + + public JoglRenderer(GL gl) { + this.gl = gl; + } + + public void setGL(GL gl) { + this.gl = gl; + } + + public Statistics getStatistics() { + return statistics; + } + + public void initialize() { + logger.log(Level.INFO, "Vendor: {0}", gl.glGetString(gl.GL_VENDOR)); + logger.log(Level.INFO, "Renderer: {0}", gl.glGetString(gl.GL_RENDERER)); + logger.log(Level.INFO, "Version: {0}", gl.glGetString(gl.GL_VERSION)); + + applyRenderState(RenderState.DEFAULT); + + powerOf2 = gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two"); + hardwareMips = gl.isExtensionAvailable("GL_SGIS_generate_mipmap"); + vbo = gl.isExtensionAvailable("GL_ARB_vertex_buffer_object"); + } + + public EnumSet getCaps() { + return caps; + } + + public void setBackgroundColor(ColorRGBA color) { + gl.glClearColor(color.r, color.g, color.b, color.a); + } + + public void cleanup() { + objManager.deleteAllObjects(this); + } + + public void resetGLObjects() { + objManager.resetObjects(); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + bits = gl.GL_COLOR_BUFFER_BIT; + } + if (depth) { + bits |= gl.GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= gl.GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + gl.glClear(bits); + } + } + + public void applyRenderState(RenderState state) { + + if (state.isWireframe() && !context.wireframe) { + gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE); + context.wireframe = true; + } else if (!state.isWireframe() && context.wireframe) { + gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL); + context.wireframe = false; + } + + + if (state.isDepthTest() && !context.depthTestEnabled) { + gl.glEnable(gl.GL_DEPTH_TEST); + gl.glDepthFunc(gl.GL_LEQUAL); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + gl.glDisable(gl.GL_DEPTH_TEST); + context.depthTestEnabled = false; + } + if (state.isAlphaTest() && !context.alphaTestEnabled) { + gl.glEnable(gl.GL_ALPHA_TEST); + gl.glAlphaFunc(gl.GL_GREATER, state.getAlphaFallOff()); + context.alphaTestEnabled = true; + } else if (!state.isAlphaTest() && context.alphaTestEnabled) { + gl.glDisable(gl.GL_ALPHA_TEST); + context.alphaTestEnabled = false; + } + if (state.isDepthWrite() && !context.depthWriteEnabled) { + gl.glDepthMask(true); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + gl.glDepthMask(false); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled) { + gl.glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + gl.glColorMask(false, false, false, false); + context.colorWriteEnabled = false; + } + if (state.isPointSprite() && !context.pointSprite) { + gl.glEnable(GL.GL_POINT_SPRITE); + gl.glTexEnvi(GL.GL_POINT_SPRITE, GL.GL_COORD_REPLACE, GL.GL_TRUE); + gl.glPointParameterf(GL.GL_POINT_SIZE_MIN, 1.0f); + } else if (!state.isPointSprite() && context.pointSprite) { + gl.glDisable(GL.GL_POINT_SPRITE); + } + if (state.isPolyOffset()) { + if (!context.polyOffsetEnabled) { + gl.glEnable(gl.GL_POLYGON_OFFSET_FILL); + gl.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) { + gl.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + gl.glDisable(gl.GL_POLYGON_OFFSET_FILL); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + gl.glDisable(gl.GL_CULL_FACE); + } else { + gl.glEnable(gl.GL_CULL_FACE); + } + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + gl.glCullFace(gl.GL_BACK); + break; + case Front: + gl.glCullFace(gl.GL_FRONT); + break; + case FrontAndBack: + gl.glCullFace(gl.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) { + gl.glDisable(gl.GL_BLEND); + } else { + gl.glEnable(gl.GL_BLEND); + } + + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE); + break; + case AlphaAdditive: + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE); + break; + case Alpha: + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA); + break; + case Color: + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_COLOR); + break; + case PremultAlpha: + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + gl.glBlendFunc(gl.GL_DST_COLOR, gl.GL_ZERO); + break; + case ModulateX2: + gl.glBlendFunc(gl.GL_DST_COLOR, gl.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + + context.blendMode = state.getBlendMode(); + } + + } + + public void onFrame() { + objManager.deleteUnused(this); + } + + public void setDepthRange(float start, float end) { + gl.glDepthRange(start, end); + } + + public void setViewPort(int x, int y, int width, int height) { + gl.glViewport(x, y, width, height); + vpX = x; + vpY = y; + vpW = width; + vpH = height; + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + gl.glEnable(gl.GL_SCISSOR_TEST); + context.clipRectEnabled = true; + } + gl.glScissor(x, y, width, height); + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + gl.glDisable(gl.GL_SCISSOR_TEST); + context.clipRectEnabled = false; + } + } + + private FloatBuffer storeMatrix(Matrix4f matrix, FloatBuffer store) { + store.rewind(); + matrix.fillFloatBuffer(store, true); + store.rewind(); + return store; + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + this.viewMatrix.set(viewMatrix); + this.projMatrix.set(projMatrix); + + if (context.matrixMode != gl.GL_PROJECTION) { + gl.glMatrixMode(gl.GL_PROJECTION); + context.matrixMode = gl.GL_PROJECTION; + } + + gl.glLoadMatrixf(storeMatrix(projMatrix, fb16)); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + this.worldMatrix.set(worldMatrix); + + if (context.matrixMode != gl.GL_MODELVIEW) { + gl.glMatrixMode(gl.GL_MODELVIEW); + context.matrixMode = gl.GL_MODELVIEW; + } + + gl.glLoadMatrixf(storeMatrix(viewMatrix, fb16)); + gl.glMultMatrixf(storeMatrix(worldMatrix, fb16)); + } + + public void setLighting(LightList list) { + if (list == null || list.size() == 0) { + // turn off lighting + gl.glDisable(gl.GL_LIGHTING); + return; + } + + gl.glEnable(gl.GL_LIGHTING); + gl.glShadeModel(gl.GL_SMOOTH); + + float[] temp = new float[4]; + + // reset model view to specify + // light positions in world space + // instead of model space +// gl.glPushMatrix(); +// gl.glLoadIdentity(); + + for (int i = 0; i < list.size() + 1; i++) { + + int lightId = gl.GL_LIGHT0 + i; + + if (list.size() <= i) { + // goes beyond the num lights we need + // disable it + gl.glDisable(lightId); + break; + } + + Light l = list.get(i); + + if (!l.isEnabled()) { + gl.glDisable(lightId); + continue; + } + + ColorRGBA color = l.getColor(); + color.toArray(temp); + + gl.glEnable(lightId); + gl.glLightfv(lightId, gl.GL_DIFFUSE, temp, 0); + gl.glLightfv(lightId, gl.GL_SPECULAR, temp, 0); + + ColorRGBA.Black.toArray(temp); + gl.glLightfv(lightId, gl.GL_AMBIENT, temp, 0); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + dl.getDirection().toArray(temp); + temp[3] = 0f; // marks to GL its a directional light + gl.glLightfv(lightId, gl.GL_POSITION, temp, 0); + break; + case Point: + PointLight pl = (PointLight) l; + pl.getPosition().toArray(temp); + temp[3] = 1f; // marks to GL its a point light + gl.glLightfv(lightId, gl.GL_POSITION, temp, 0); + break; + } + + } + + // restore modelview to original value +// gl.glPopMatrix(); + } + + public void deleteShaderSource(ShaderSource source) { + } + + public void setShader(Shader shader) { + } + + public void deleteShader(Shader shader) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + } + + public void setFrameBuffer(FrameBuffer fb) { + } + + public void deleteFrameBuffer(FrameBuffer fb) { + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + if (fb != null) { + return; + } + + gl.glReadPixels(vpX, vpY, vpW, vpH, gl.GL_BGRA, gl.GL_UNSIGNED_BYTE, byteBuf); + } + + private int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return gl.GL_TEXTURE_2D; + case TwoDimensionalArray: + return gl.GL_TEXTURE_2D_ARRAY_EXT; + case ThreeDimensional: + return gl.GL_TEXTURE_3D; + case CubeMap: + return gl.GL_TEXTURE_CUBE_MAP; + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return gl.GL_LINEAR; + case Nearest: + return gl.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return gl.GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return gl.GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return gl.GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return gl.GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return gl.GL_LINEAR; + case NearestNoMipMaps: + return gl.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { + case BorderClamp: + return gl.GL_CLAMP_TO_BORDER; + case Clamp: + return gl.GL_CLAMP; + case EdgeClamp: + return gl.GL_CLAMP_TO_EDGE; + case Repeat: + return gl.GL_REPEAT; + case MirroredRepeat: + return gl.GL_MIRRORED_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()); + gl.glTexParameteri(target, gl.GL_TEXTURE_MIN_FILTER, minFilter); + gl.glTexParameteri(target, gl.GL_TEXTURE_MAG_FILTER, magFilter); + + // repeat modes + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: + gl.glTexParameteri(target, gl.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + gl.glTexParameteri(target, gl.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + // fall down here is intentional.. +// case OneDimensional: + gl.glTexParameteri(target, gl.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) { + gl.glTexParameteri(target, GL.GL_TEXTURE_COMPARE_MODE, GL.GL_COMPARE_R_TO_TEXTURE); + gl.glTexParameteri(target, GL.GL_DEPTH_TEXTURE_MODE, GL.GL_INTENSITY); + if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) { + gl.glTexParameteri(target, GL.GL_TEXTURE_COMPARE_FUNC, GL.GL_GEQUAL); + } else { + gl.glTexParameteri(target, GL.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL); + } + } + } + + public void updateTexImageData(Image image, Texture.Type type, boolean mips) { + int texId = image.getId(); + if (texId == -1) { + // create texture + gl.glGenTextures(1, ib1); + texId = ib1.get(0); + image.setId(texId); + objManager.registerForCleanup(image); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type); + if (context.boundTextures[0] != image) { + if (context.boundTextureUnit != 0) { + gl.glActiveTexture(gl.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + + gl.glBindTexture(target, texId); + context.boundTextures[0] = image; + } + + boolean generateMips = false; + if (!image.hasMipmaps() && mips) { + // No pregenerated mips available, + // generate from base level if required + if (hardwareMips) { + gl.glTexParameteri(target, GL.GL_GENERATE_MIPMAP, gl.GL_TRUE); + } else { + generateMips = true; + } + } + + TextureUtil.uploadTexture(gl, image, 0, generateMips, powerOf2); + + + image.clearUpdateNeeded(); + } + + private void checkTexturingUsed() { + IDList textureList = context.textureIndexList; + // old mesh used texturing, new mesh doesn't use it + // should actually go through entire oldLen and + // disable texturing for each unit.. but that's for later. + if (textureList.oldLen > 0 && textureList.newLen == 0) { + gl.glDisable(gl.GL_TEXTURE_2D); + } + } + + public void setTexture(int unit, Texture tex) { + Image image = tex.getImage(); + if (image.isUpdateNeeded()) { + updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels()); + } + + 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) { + gl.glActiveTexture(gl.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + gl.glEnable(type); + } + + if (textures[unit] != image) { + if (context.boundTextureUnit != unit) { + gl.glActiveTexture(gl.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + gl.glBindTexture(type, texId); + textures[unit] = image; + } + } + + 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) { + gl.glActiveTexture(gl.GL_TEXTURE0 + idx); + context.boundTextureUnit = idx; + } + // XXX: Uncomment me + //gl.glDisable(convertTextureType(textures[idx].getType())); + textures[idx] = null; + } + context.textureIndexList.copyNewToOld(); + } + + public void deleteImage(Image image) { + int texId = image.getId(); + if (texId != -1) { + ib1.put(0, texId); + ib1.position(0).limit(1); + gl.glDeleteTextures(1, ib1); + image.resetObject(); + } + } + + private int convertUsage(Usage usage) { + switch (usage) { + case Static: + return gl.GL_STATIC_DRAW; + case Dynamic: + return gl.GL_DYNAMIC_DRAW; + case Stream: + return gl.GL_STREAM_DRAW; + default: + throw new RuntimeException("Unknown usage type: " + usage); + } + } + + public void updateBufferData(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId == -1) { + // create buffer + gl.glGenBuffers(1, ib1); + bufId = ib1.get(0); + vb.setId(bufId); + objManager.registerForCleanup(vb); + } + + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = gl.GL_ELEMENT_ARRAY_BUFFER; + if (context.boundElementArrayVBO != bufId) { + gl.glBindBuffer(target, bufId); + context.boundElementArrayVBO = bufId; + } + } else { + target = gl.GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + gl.glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + Buffer data = vb.getData(); + data.rewind(); + + gl.glBufferData(target, + data.capacity() * vb.getFormat().getComponentSize(), + data, + usage); + + vb.clearUpdateNeeded(); + } + + public void deleteBuffer(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + ib1.put(0, bufId); + ib1.position(0).limit(1); + gl.glDeleteBuffers(1, ib1); + vb.resetObject(); + } + } + + private int convertArrayType(VertexBuffer.Type type) { + switch (type) { + case Position: + return gl.GL_VERTEX_ARRAY; + case Normal: + return gl.GL_NORMAL_ARRAY; + case TexCoord: + return gl.GL_TEXTURE_COORD_ARRAY; + case Color: + return gl.GL_COLOR_ARRAY; + default: + return -1; // unsupported + } + } + + private int convertVertexFormat(VertexBuffer.Format fmt) { + switch (fmt) { + case Byte: + return gl.GL_BYTE; + case Double: + return gl.GL_DOUBLE; + case Float: + return gl.GL_FLOAT; + case Half: + return gl.GL_HALF_FLOAT_ARB; + case Int: + return gl.GL_INT; + case Short: + return gl.GL_SHORT; + case UnsignedByte: + return gl.GL_UNSIGNED_BYTE; + case UnsignedInt: + return gl.GL_UNSIGNED_INT; + case UnsignedShort: + return gl.GL_UNSIGNED_SHORT; + default: + throw new UnsupportedOperationException("Unrecognized vertex format: " + fmt); + } + } + + private int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return gl.GL_POINTS; + case Lines: + return gl.GL_LINES; + case LineLoop: + return gl.GL_LINE_LOOP; + case LineStrip: + return gl.GL_LINE_STRIP; + case Triangles: + return gl.GL_TRIANGLES; + case TriangleFan: + return gl.GL_TRIANGLE_FAN; + case TriangleStrip: + return gl.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + private void setVertexAttribVBO(VertexBuffer vb, VertexBuffer idb) { + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) { + return; // unsupported + } + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + int bufId = idb != null ? idb.getId() : vb.getId(); + if (context.boundArrayVBO != bufId) { + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + } + + gl.glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal) { + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled) { + gl.glEnable(gl.GL_NORMALIZE); + context.normalizeEnabled = true; + } else if (!vb.isNormalized() && context.normalizeEnabled) { + gl.glDisable(gl.GL_NORMALIZE); + context.normalizeEnabled = false; + } + } + + int comps = vb.getNumComponents(); + int type = convertVertexFormat(vb.getFormat()); + + switch (vb.getBufferType()) { + case Position: + gl.glVertexPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + case Normal: + gl.glNormalPointer(type, vb.getStride(), vb.getOffset()); + break; + case Color: + gl.glColorPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + case TexCoord: + gl.glTexCoordPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + } + } + + private void drawTriangleListVBO(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) { + gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, bufId); + context.boundElementArrayVBO = bufId; + } + + if (mesh.getMode() == Mode.Hybrid) { + 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]; + gl.glDrawElements(elMode, + elementLength, + fmt, + curOffset); + curOffset += elementLength * elSize; + } + } else { + gl.glDrawElements(convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertVertexFormat(indexBuf.getFormat()), + 0); + } + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) { + return; // unsupported + } + gl.glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal) { + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled) { + gl.glEnable(gl.GL_NORMALIZE); + context.normalizeEnabled = true; + } else if (!vb.isNormalized() && context.normalizeEnabled) { + gl.glDisable(gl.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.clear(); + data.position(vb.getOffset()); + + switch (vb.getBufferType()) { + case Position: + gl.glVertexPointer(comps, type, vb.getStride(), data); + break; + case Normal: + gl.glNormalPointer(type, vb.getStride(), data); + break; + case Color: + gl.glColorPointer(comps, type, vb.getStride(), data); + break; + case TexCoord: + gl.glTexCoordPointer(comps, type, vb.getStride(), data); + break; + } + } + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + public void clearVertexAttribs() { + for (int i = 0; i < 16; i++) { + VertexBuffer vb = context.boundAttribs[i]; + if (vb != null) { + int arrayType = convertArrayType(vb.getBufferType()); + gl.glDisableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = null; + } + } + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + Mesh.Mode mode = mesh.getMode(); + + 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 = 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); + gl.glDrawElements(elMode, + elementLength, + fmt, + indexData); + curOffset += elementLength; + } + } else { + gl.glDrawElements(convertElementMode(mode), + indexData.capacity(), + convertVertexFormat(indexBuf.getFormat()), + indexData); + } + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + IntMap buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry entry : buffers) { + VertexBuffer vb = entry.getValue(); + + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly) // ignore cpu-only buffers + { + continue; + } + + if (vb.getBufferType() == Type.Index) { + indices = vb; + } else { + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + gl.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshVBO(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + IntMap buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry 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 + setVertexAttribVBO(vb, null); + } else { + // interleaved + setVertexAttribVBO(vb, interleavedData); + } + } + + if (indices != null) { + drawTriangleListVBO(indices, mesh, count); + } else { + gl.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void updateDisplayList(Mesh mesh) { + if (mesh.getId() != -1) { + // delete list first + gl.glDeleteLists(mesh.getId(), mesh.getId()); + mesh.setId(-1); + } + + // create new display list + // first set state to NULL + applyRenderState(RenderState.NULL); + + // disable lighting + setLighting(null); + + int id = gl.glGenLists(1); + mesh.setId(id); + gl.glNewList(id, gl.GL_COMPILE); + renderMeshDefault(mesh, 0, 1); + gl.glEndList(); + } + + private void renderMeshDisplayList(Mesh mesh) { + if (mesh.getId() == -1) { + updateDisplayList(mesh); + } + gl.glCallList(mesh.getId()); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (mesh.getVertexCount() == 0) + return; + if (context.pointSize != mesh.getPointSize()) { + gl.glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + gl.glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + checkTexturingUsed(); + + if (vbo) { + renderMeshVBO(mesh, lod, count); + } else { + boolean dynamic = false; + if (mesh.getNumLodLevels() == 0) { + IntMap bufs = mesh.getBuffers(); + for (Entry entry : bufs) { + if (entry.getValue().getUsage() != VertexBuffer.Usage.Static) { + dynamic = true; + break; + } + } + } else { + dynamic = true; + } + + if (!dynamic) { + // dealing with a static object, generate display list + renderMeshDisplayList(mesh); + } else { + renderMeshDefault(mesh, lod, count); + } + } + } + + public void setAlphaToCoverage(boolean value) { + if (value) { + gl.glEnable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + gl.glDisable(gl.GL_SAMPLE_ALPHA_TO_COVERAGE); + } + } +} diff --git a/engine/src/jogl/com/jme3/renderer/jogl/TextureUtil.java b/engine/src/jogl/com/jme3/renderer/jogl/TextureUtil.java new file mode 100644 index 000000000..dcc41f31f --- /dev/null +++ b/engine/src/jogl/com/jme3/renderer/jogl/TextureUtil.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.jogl; + +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; +import javax.media.opengl.GL; + +public class TextureUtil { + + public static int convertTextureFormat(Format fmt){ + switch (fmt){ + case Alpha16: + case Alpha8: + return GL.GL_ALPHA; + case Luminance8Alpha8: + case Luminance16Alpha16: + return GL.GL_LUMINANCE_ALPHA; + case Luminance8: + case Luminance16: + return GL.GL_LUMINANCE; + case RGB10: + case RGB16: + case BGR8: + case RGB8: + case RGB565: + return GL.GL_RGB; + case RGB5A1: + case RGBA16: + case RGBA8: + return GL.GL_RGBA; + default: + throw new UnsupportedOperationException("Unrecognized format: "+fmt); + } + } + + public static void uploadTexture(GL gl, + Image img, + int index, + boolean generateMips, + boolean powerOf2){ + + 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 format = -1; + int internalFormat = -1; + int dataType = -1; + + switch (fmt){ + case Alpha16: + format = gl.GL_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_ALPHA16; + break; + case Alpha8: + format = gl.GL_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_ALPHA8; + break; + case Luminance8: + format = gl.GL_LUMINANCE; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_LUMINANCE8; + break; + case Luminance8Alpha8: + format = gl.GL_LUMINANCE_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_LUMINANCE8_ALPHA8; + break; + case Luminance16Alpha16: + format = gl.GL_LUMINANCE_ALPHA; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_LUMINANCE16_ALPHA16; + break; + case Luminance16: + format = gl.GL_LUMINANCE; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_LUMINANCE16; + break; + case RGB565: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_SHORT_5_6_5; + internalFormat = gl.GL_RGB8; + break; + case ARGB4444: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_SHORT_4_4_4_4; + internalFormat = gl.GL_RGBA4; + break; + case RGB10: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_RGB10; + break; + case RGB16: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_RGB16; + break; + case RGB5A1: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_SHORT_5_5_5_1; + internalFormat = gl.GL_RGB5_A1; + break; + case RGB8: + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_RGB8; + break; + case BGR8: + format = gl.GL_BGR; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_RGB8; + break; + case RGBA16: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_RGBA16; + break; + case RGBA8: + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + internalFormat = gl.GL_RGBA8; + break; + case DXT1: + compress = true; + internalFormat = gl.GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + format = gl.GL_RGB; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case DXT1A: + compress = true; + internalFormat = gl.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case DXT3: + compress = true; + internalFormat = gl.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + case DXT5: + compress = true; + internalFormat = gl.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + format = gl.GL_RGBA; + dataType = gl.GL_UNSIGNED_BYTE; + break; + default: + throw new UnsupportedOperationException("Unrecognized format: "+fmt); + } + + if (data != null) + gl.glPixelStorei(gl.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 }; + } + + 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){ + gl.glCompressedTexImage2D(gl.GL_TEXTURE_2D, + i, + internalFormat, + mipWidth, + mipHeight, + 0, + data.remaining(), + data); + }else{ + gl.glTexImage2D(gl.GL_TEXTURE_2D, + i, + internalFormat, + mipWidth, + mipHeight, + 0, + format, + dataType, + data); + } + + pos += mipSizes[i]; + } + } + +} diff --git a/engine/src/jogl/com/jme3/system/jogl/JoglAbstractDisplay.java b/engine/src/jogl/com/jme3/system/jogl/JoglAbstractDisplay.java new file mode 100644 index 000000000..cb2cef859 --- /dev/null +++ b/engine/src/jogl/com/jme3/system/jogl/JoglAbstractDisplay.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.system.jogl; + +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.renderer.jogl.JoglRenderer; +import com.sun.opengl.util.Animator; +import com.sun.opengl.util.FPSAnimator; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; +import javax.media.opengl.DebugGL; +import javax.media.opengl.GL; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCanvas; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.TraceGL; + +public abstract class JoglAbstractDisplay extends JoglContext implements GLEventListener { + + private static final Logger logger = Logger.getLogger(JoglAbstractDisplay.class.getName()); + + protected GraphicsDevice device; + protected GLCanvas canvas; + protected Animator animator; + protected AtomicBoolean active = new AtomicBoolean(false); + + protected boolean wasActive = false; + protected int frameRate; + protected boolean useAwt = true; + protected AtomicBoolean autoFlush = new AtomicBoolean(true); + protected boolean wasAnimating = false; + + protected void initGLCanvas(){ + device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + + GLCapabilities caps = new GLCapabilities(); + caps.setHardwareAccelerated(true); + caps.setDoubleBuffered(true); + caps.setStencilBits(settings.getStencilBits()); + caps.setDepthBits(settings.getDepthBits()); + + if (settings.getSamples() > 1){ + caps.setSampleBuffers(true); + caps.setNumSamples(settings.getSamples()); + } + + canvas = new GLCanvas(caps){ + @Override + public void addNotify(){ + super.addNotify(); + onCanvasAdded(); + } + @Override + public void removeNotify(){ + onCanvasRemoved(); + super.removeNotify(); + } + }; + if (settings.isVSync()){ + canvas.getGL().setSwapInterval(1); + } + canvas.setFocusable(true); + canvas.setIgnoreRepaint(true); + canvas.addGLEventListener(this); + + GL gl = canvas.getGL(); +// if (false){ + // trace mode + // jME already uses err stream, use out instead +// gl = new TraceGL(gl, System.out); +// }else if (false){ + // debug mode +// gl = new DebugGL(gl); +// }else{ + // production mode +// } + renderer = new JoglRenderer(gl); + } + + protected void startGLCanvas(){ + if (frameRate > 0){ + animator = new FPSAnimator(canvas, frameRate); + animator.setRunAsFastAsPossible(true); + }else{ + animator = new Animator(canvas); + animator.setRunAsFastAsPossible(true); + } + + animator.start(); + wasAnimating = true; + } + + protected void onCanvasAdded(){ + } + + protected void onCanvasRemoved(){ + } + + @Override + public KeyInput getKeyInput(){ + return new AwtKeyInput(canvas); + } + + @Override + public MouseInput getMouseInput(){ + return new AwtMouseInput(canvas); + } + + public void setAutoFlushFrames(boolean enabled){ + autoFlush.set(enabled); + } + + /** + * Callback. + */ + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { + listener.reshape(width, height); + } + + /** + * Callback. + */ + public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { + } + +} diff --git a/engine/src/jogl/com/jme3/system/jogl/JoglCanvas.java b/engine/src/jogl/com/jme3/system/jogl/JoglCanvas.java new file mode 100644 index 000000000..f85a9cb2d --- /dev/null +++ b/engine/src/jogl/com/jme3/system/jogl/JoglCanvas.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.system.jogl; + +import com.jme3.system.JmeCanvasContext; +import java.awt.Canvas; +import java.util.logging.Logger; +import javax.media.opengl.GLAutoDrawable; + +public class JoglCanvas extends JoglAbstractDisplay implements JmeCanvasContext { + + private static final Logger logger = Logger.getLogger(JoglCanvas.class.getName()); + private int width, height; + + public JoglCanvas(){ + super(); + initGLCanvas(); + } + + public Type getType() { + return Type.Canvas; + } + + public void setTitle(String title) { + } + + public void restart() { + } + + public void create(boolean waitFor){ + if (waitFor) + waitFor(true); + } + + public void destroy(boolean waitFor){ + if (waitFor) + waitFor(false); + } + + @Override + protected void onCanvasRemoved(){ + super.onCanvasRemoved(); + created.set(false); + waitFor(false); + } + + @Override + protected void onCanvasAdded(){ + startGLCanvas(); + } + + public void init(GLAutoDrawable drawable) { + canvas.requestFocus(); + + super.internalCreate(); + logger.info("Display created."); + + renderer.initialize(); + listener.initialize(); + } + + public void display(GLAutoDrawable glad) { + if (!created.get() && renderer != null){ + listener.destroy(); + logger.info("Canvas destroyed."); + super.internalDestroy(); + return; + } + + if (width != canvas.getWidth() || height != canvas.getHeight()){ + width = canvas.getWidth(); + height = canvas.getHeight(); + if (listener != null) + listener.reshape(width, height); + } + + boolean flush = autoFlush.get(); + if (flush && !wasAnimating){ + animator.start(); + wasAnimating = true; + }else if (!flush && wasAnimating){ + animator.stop(); + wasAnimating = false; + } + + listener.update(); + renderer.onFrame(); + + } + + public Canvas getCanvas() { + return canvas; + } + +} diff --git a/engine/src/jogl/com/jme3/system/jogl/JoglContext.java b/engine/src/jogl/com/jme3/system/jogl/JoglContext.java new file mode 100644 index 000000000..6f36f3092 --- /dev/null +++ b/engine/src/jogl/com/jme3/system/jogl/JoglContext.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.jogl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.jogl.JoglRenderer; +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.JmeContext; +import com.jme3.system.NanoTimer; +import com.jme3.system.Timer; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class JoglContext implements JmeContext { + + protected AtomicBoolean created = new AtomicBoolean(false); + protected final Object createdLock = new Object(); + + protected AppSettings settings = new AppSettings(true); + protected JoglRenderer renderer; + protected Timer timer; + protected SystemListener listener; + + protected AwtKeyInput keyInput; + protected AwtMouseInput mouseInput; + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + } + + public AppSettings getSettings() { + return settings; + } + + public Renderer getRenderer() { + return renderer; + } + + public MouseInput getMouseInput() { + return mouseInput; + } + + public KeyInput getKeyInput() { + return keyInput; + } + + public JoyInput getJoyInput() { + return null; + } + + public Timer getTimer() { + return timer; + } + + public boolean isCreated() { + return created.get(); + } + + 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 void internalCreate() { + timer = new NanoTimer(); + synchronized (createdLock){ + created.set(true); + createdLock.notifyAll(); + } + // renderer initialization must happen in subclass. + } + + protected void internalDestroy() { + renderer = null; + timer = null; + + synchronized (createdLock){ + created.set(false); + createdLock.notifyAll(); + } + } + +} diff --git a/engine/src/jogl/com/jme3/system/jogl/JoglDisplay.java b/engine/src/jogl/com/jme3/system/jogl/JoglDisplay.java new file mode 100644 index 000000000..738423b79 --- /dev/null +++ b/engine/src/jogl/com/jme3/system/jogl/JoglDisplay.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.system.jogl; + +import com.jme3.system.AppSettings; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.DisplayMode; +import java.awt.Frame; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.media.opengl.GLAutoDrawable; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +public class JoglDisplay extends JoglAbstractDisplay { + + private static final Logger logger = Logger.getLogger(JoglDisplay.class.getName()); + + protected AtomicBoolean windowCloseRequest = new AtomicBoolean(false); + protected AtomicBoolean needClose = new AtomicBoolean(false); + protected AtomicBoolean needRestart = new AtomicBoolean(false); + protected boolean wasInited = false; + protected Frame frame; + + public Type getType() { + return Type.Display; + } + + protected DisplayMode getFullscreenDisplayMode(DisplayMode[] modes, int width, int height, int bpp, int freq){ + for (DisplayMode mode : modes){ + if (mode.getWidth() == width + && mode.getHeight() == height + && (mode.getBitDepth() == DisplayMode.BIT_DEPTH_MULTI + || mode.getBitDepth() == bpp + || (mode.getBitDepth() == 32 && bpp==24)) + && mode.getRefreshRate() == freq){ + return mode; + } + } + return null; + } + + protected void createGLFrame(){ + Container contentPane; + if (useAwt){ + frame = new Frame(settings.getTitle()); + contentPane = frame; + }else{ + frame = new JFrame(settings.getTitle()); + contentPane = ((JFrame)frame).getContentPane(); + } + + contentPane.setLayout(new BorderLayout()); + + applySettings(settings); + + frame.setResizable(false); + frame.setFocusable(true); + + if (settings.getIcons() != null) { + try { + Method setIconImages = frame.getClass().getMethod("setIconImages", List.class); + setIconImages.invoke(frame, Arrays.asList(settings.getIcons())); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // only add canvas after frame is visible + contentPane.add(canvas, BorderLayout.CENTER); + frame.pack(); +// frame.setSize(contentPane.getPreferredSize()); + + if (device.getFullScreenWindow() == null){ + // now that canvas is attached, + // determine optimal size to contain it + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation((screenSize.width - frame.getWidth()) / 2, + (screenSize.height - frame.getHeight()) / 2); + } + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent evt) { + windowCloseRequest.set(true); + } + @Override + public void windowActivated(WindowEvent evt) { + active.set(true); + } + + @Override + public void windowDeactivated(WindowEvent evt) { + active.set(false); + } + }); + } + + protected void applySettings(AppSettings settings){ + DisplayMode displayMode; + if (settings.getWidth() <= 0 || settings.getHeight() <= 0){ + displayMode = device.getDisplayMode(); + settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); + }else if (settings.isFullscreen()){ + displayMode = getFullscreenDisplayMode(device.getDisplayModes(), + 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(), 0, 0); + } + + // FIXME: seems to return false even though + // it is supported.. +// if (!device.isDisplayChangeSupported()){ +// // must use current device mode if display mode change not supported +// displayMode = device.getDisplayMode(); +// settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); +// } + + frameRate = settings.getFrameRate(); + logger.log(Level.INFO, "Selected display mode: {0}x{1}x{2} @{3}", + new Object[]{displayMode.getWidth(), + displayMode.getHeight(), + displayMode.getBitDepth(), + displayMode.getRefreshRate()}); + + canvas.setSize(displayMode.getWidth(), displayMode.getHeight()); + + DisplayMode prevDisplayMode = device.getDisplayMode(); + + if (settings.isFullscreen() && device.isFullScreenSupported()){ + frame.setUndecorated(true); + + try{ + device.setFullScreenWindow(frame); + if (!prevDisplayMode.equals(displayMode) + && device.isDisplayChangeSupported()){ + device.setDisplayMode(displayMode); + } + } catch (Throwable t){ + logger.log(Level.SEVERE, "Failed to enter fullscreen mode", t); + device.setFullScreenWindow(null); + } + }else{ + if (!device.isFullScreenSupported()){ + logger.warning("Fullscreen not supported."); + }else{ + frame.setUndecorated(false); + device.setFullScreenWindow(null); + } + + frame.setVisible(true); + } + } + + private void initInEDT(){ + initGLCanvas(); + + createGLFrame(); + + startGLCanvas(); + } + + public void init(GLAutoDrawable drawable){ + // prevent initializing twice on restart + if (!wasInited){ + canvas.requestFocus(); + + super.internalCreate(); + logger.info("Display created."); + + renderer.initialize(); + listener.initialize(); + + wasInited = true; + } + } + + public void create(boolean waitFor){ + try { + if (waitFor){ + try{ + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + initInEDT(); + } + }); + } catch (InterruptedException ex) { + listener.handleError("Interrupted", ex); + } + }else{ + SwingUtilities.invokeLater(new Runnable() { + public void run() { + initInEDT(); + } + }); + } + } catch (InvocationTargetException ex) { + throw new AssertionError(); // can never happen + } + } + + public void destroy(boolean waitFor){ + needClose.set(true); + if (waitFor){ + waitFor(false); + } + } + + public void restart() { + if (created.get()){ + needRestart.set(true); + }else{ + throw new IllegalStateException("Display not started yet. Cannot restart"); + } + } + + public void setTitle(String title){ + if (frame != null) + frame.setTitle(title); + } + + /** + * Callback. + */ + public void display(GLAutoDrawable drawable) { + if (needClose.get()) { + listener.destroy(); + animator.stop(); + if (settings.isFullscreen()) { + device.setFullScreenWindow(null); + } + frame.dispose(); + logger.info("Display destroyed."); + super.internalDestroy(); + return; + } + + if (windowCloseRequest.get()){ + listener.requestClose(false); + windowCloseRequest.set(false); + } + + if (needRestart.getAndSet(false)){ + // for restarting contexts + if (frame.isVisible()){ + animator.stop(); + frame.dispose(); + createGLFrame(); + startGLCanvas(); + } + } + +// boolean flush = autoFlush.get(); +// if (animator.isAnimating() != flush){ +// if (flush) +// animator.stop(); +// else +// animator.start(); +// } + + if (wasActive != active.get()){ + if (!wasActive){ + listener.gainFocus(); + wasActive = true; + }else{ + listener.loseFocus(); + wasActive = false; + } + } + + listener.update(); + renderer.onFrame(); + } +} diff --git a/engine/src/jogl2/com/jme3/glhelper/Helper.java b/engine/src/jogl2/com/jme3/glhelper/Helper.java new file mode 100644 index 000000000..dc9f85006 --- /dev/null +++ b/engine/src/jogl2/com/jme3/glhelper/Helper.java @@ -0,0 +1,85 @@ +package com.jme3.glhelper; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.jme3.math.ColorRGBA; +import com.jme3.shader.Shader; + +/** + * OpenGL helper that does not rely on a specific binding. Its main purpose is to allow + * to move the binding-agnostic OpenGL logic from the source code of the both renderers + * to a single abstract renderer in order to ease the maintenance + * + * @author Julien Gouesse + * + */ +public interface Helper { + + public static final int TEXTURE0 = 33984; + + //TODO: add Array, Format, TargetBuffer, TextureType, ShaderType, ShadeModel, BlendMode, CullFace, FillMode, DepthFunc, AlphaFunc + + public enum MatrixMode{ + + MODELVIEW(0x1700),PROJECTION(0x1701); + + private final int glConstant; + + private MatrixMode(int glConstant){ + this.glConstant = glConstant; + } + + public final int getGLConstant(){ + return glConstant; + } + }; + + public enum BufferBit{ + COLOR_BUFFER(16384),DEPTH_BUFFER(256),STENCIL_BUFFER(1024),ACCUM_BUFFER(512); + + private final int glConstant; + + private BufferBit(int glConstant){ + this.glConstant = glConstant; + } + + public final int getGLConstant(){ + return glConstant; + } + } + + public enum Filter{ + NEAREST(9728),LINEAR(9729); + + private final int glConstant; + + private Filter(int glConstant){ + this.glConstant = glConstant; + } + + public final int getGLConstant(){ + return glConstant; + } + } + + public void useProgram(int program); + + public void setMatrixMode(MatrixMode matrixMode); + + public void loadMatrixf(FloatBuffer m); + + public void multMatrixf(FloatBuffer m); + + public void setViewPort(int x, int y, int width, int height); + + public void setBackgroundColor(ColorRGBA color); + + public void clear(BufferBit bufferBit); + + public void setDepthRange(float start, float end); + + public void setScissor(int x, int y, int width, int height); + + public int getUniformLocation(Shader shader,String name,ByteBuffer nameBuffer); +} diff --git a/engine/src/jogl2/com/jme3/glhelper/jogl/JoglHelper.java b/engine/src/jogl2/com/jme3/glhelper/jogl/JoglHelper.java new file mode 100644 index 000000000..242bca348 --- /dev/null +++ b/engine/src/jogl2/com/jme3/glhelper/jogl/JoglHelper.java @@ -0,0 +1,96 @@ +package com.jme3.glhelper.jogl; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import javax.media.opengl.GL; +import javax.media.opengl.GLContext; + +import com.jme3.glhelper.Helper; +import com.jme3.math.ColorRGBA; +import com.jme3.shader.Shader; + +/** + * OpenGL helper that relies on a specific binding. The main purposes of this helper is to contain + * the type-safe OpenGL logic that is specific to a particular binding and to support several kinds + * of hardware by delegating tasks supported by only some "profiles" to separate helpers + * (desktop shader-based, desktop fixed pipeline, embedded shader-based, embedded fixed pipeline) + * + * @author Julien Gouesse + * + */ +public class JoglHelper implements Helper { + + + public JoglHelper(){ + //TODO: get the current GL and choose the best fitted delegate depending on the hardware (Desktop, Embedded) + GL gl = GLContext.getCurrentGL(); + if(gl.isGL2ES1()){ + //embedded fixed pipeline + } + else{ + if(gl.isGL2ES2()){ + //embedded shader-based + } + else{ + if(gl.isGL3()&&!gl.isGL3bc()||gl.isGL4()&&!gl.isGL4bc()){ + //desktop shader-based (forward compatible) + } + else{ + //if GLSL is supported, use desktop shader-based (backward compatible) + //otherwise use desktop fixed pipeline + } + } + } + } + + @Override + public void useProgram(int program) { + GLContext.getCurrentGL().getGL2().glUseProgram(program); + } + + @Override + public void setMatrixMode(MatrixMode matrixMode){ + GLContext.getCurrentGL().getGL2().glMatrixMode(matrixMode.getGLConstant()); + } + + @Override + public void loadMatrixf(FloatBuffer m){ + GLContext.getCurrentGL().getGL2().glLoadMatrixf(m); + } + + @Override + public void multMatrixf(FloatBuffer m){ + GLContext.getCurrentGL().getGL2().glMultMatrixf(m); + } + + @Override + public void setViewPort(int x, int y, int width, int height){ + GLContext.getCurrentGL().glViewport(x, y, width, height); + } + + @Override + public void setBackgroundColor(ColorRGBA color){ + GLContext.getCurrentGL().glClearColor(color.r, color.g, color.b, color.a); + } + + @Override + public void clear(BufferBit bufferBit){ + GLContext.getCurrentGL().glClear(bufferBit.getGLConstant()); + } + + @Override + public void setDepthRange(float start, float end) { + GLContext.getCurrentGL().glDepthRange(start, end); + } + + @Override + public void setScissor(int x, int y, int width, int height){ + GLContext.getCurrentGL().glScissor(x, y, width, height); + } + + @Override + public int getUniformLocation(Shader shader,String name,ByteBuffer nameBuffer){ + return GLContext.getCurrentGL().getGL2ES2().glGetUniformLocation(shader.getId(),name); + } +} diff --git a/engine/src/jogl2/com/jme3/renderer/AbstractRenderer.java b/engine/src/jogl2/com/jme3/renderer/AbstractRenderer.java new file mode 100644 index 000000000..6fe59a06f --- /dev/null +++ b/engine/src/jogl2/com/jme3/renderer/AbstractRenderer.java @@ -0,0 +1,756 @@ +package com.jme3.renderer; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.EnumSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.jme3.glhelper.Helper; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +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.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.shader.Uniform; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; +import com.jme3.texture.FrameBuffer.RenderBuffer; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import com.jme3.util.ListMap; + +/** + * OpenGL renderer that does not rely on a specific binding, that uses low-level OpenGL operations to + * perform higher-level tasks except those that requires the use of binding-specific features and that + * handles binding-agnostic OpenGL operations that cannot be easily made type-safe (enable, disable, ...) + * + * @author Julien Gouesse + * + */ +public abstract class AbstractRenderer implements Renderer { + + protected static final Logger logger = Logger.getLogger(AbstractRenderer.class.getName()); + + protected static final boolean VALIDATE_SHADER = false; + + protected final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + + protected final StringBuilder stringBuf = new StringBuilder(250); + + protected Statistics statistics = new Statistics(); + + protected final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); + + protected final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + + protected RenderContext context = new RenderContext(); + + protected GLObjectManager objManager = new GLObjectManager(); + + protected EnumSet caps = EnumSet.noneOf(Caps.class); + + protected Shader boundShader; + + protected int initialDrawBuf, initialReadBuf; + + protected int glslVer; + + protected int vertexTextureUnits; + + protected int fragTextureUnits; + + protected int vertexUniforms; + + protected int fragUniforms; + + protected int vertexAttribs; + + protected int maxFBOSamples; + + protected int maxFBOAttachs; + + protected int maxMRTFBOAttachs; + + protected int maxRBSize; + + protected int maxTexSize; + + protected int maxCubeTexSize; + + protected int maxVertCount; + + protected int maxTriCount; + + protected boolean tdc; + + protected int vpX, vpY, vpW, vpH; + + protected FrameBuffer lastFb = null; + + protected Matrix4f worldMatrix = new Matrix4f(); + + protected Matrix4f viewMatrix = new Matrix4f(); + + protected Matrix4f projMatrix = new Matrix4f(); + + protected FloatBuffer fb16 = BufferUtils.createFloatBuffer(16); + + // TODO: these flags should be moved into the Caps flags + protected boolean powerOf2 = false; + + protected boolean hardwareMips = false; + + protected boolean vbo = false; + + protected boolean framebufferBlit = false; + + protected boolean renderbufferStorageMultisample = false; + + protected Helper helper; + + protected FloatBuffer storeMatrix(Matrix4f matrix, FloatBuffer store) { + store.rewind(); + matrix.fillFloatBuffer(store, true); + store.rewind(); + return store; + } + + 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 getCaps() { + return caps; + } + + public void cleanup() { + objManager.deleteAllObjects(this); + statistics.clearMemory(); + } + + public void setBackgroundColor(ColorRGBA color) { + helper.setBackgroundColor(color); + } + + public void resetGLObjects() { + objManager.resetObjects(); + statistics.clearMemory(); + boundShader = null; + lastFb = null; + context.reset(); + } + + public void onFrame() { + objManager.deleteUnused(this); + // statistics.clearFrame(); + } + + protected void updateShaderUniforms(Shader shader) { + ListMap 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 updateUniform(Shader shader, Uniform uniform) { + if (glslVer != -1) { + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shader.getId() > 0; + if (context.boundShaderProgram != shaderId) { + helper.useProgram(shaderId); + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } + else { + statistics.onShaderUse(shader, false); + } + + updateUniformVar(shader, uniform); + } + } + + protected void resetUniformLocations(Shader shader) { + ListMap 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 + } + } + + protected 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 buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } + else { + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry entry : buffers) { + VertexBuffer vb = entry.getValue(); + + if (vb.getBufferType() == Type.InterleavedData || vb.getUsage() == Usage.CpuOnly + || vb.getBufferType() == Type.Index) { + continue; + } + + /*if (vb.getBufferType() == Type.Index) { + indices = vb; + } + else {*/ + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } + else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + /*}*/ + } + + if (indices != null) { + drawTriangleList(indices, mesh, count); + } + else { + drawArrays(mesh); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + protected void renderMeshVBO(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + IntMap buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } + else { + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry 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 + setVertexAttribVBO(vb, null); + } + else { + // interleaved + setVertexAttribVBO(vb, interleavedData); + } + } + + if (indices != null) { + drawTriangleListVBO(indices, mesh, count); + } + else { + drawArrays(mesh); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + protected abstract void setVertexAttribVBO(VertexBuffer vb, VertexBuffer idb); + + public abstract void setVertexAttrib(VertexBuffer vb, VertexBuffer idb); + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + Mesh.Mode mode = mesh.getMode(); + Buffer indexData = indexBuf.getData(); + indexData.clear(); + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + Mode elMode = Mode.Triangles; + // 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 = Mode.TriangleStrip; + } + else if (i == fanStart) { + elMode = Mode.TriangleStrip; + } + int elementLength = elementLengths[i]; + indexData.position(curOffset); + drawElements(elMode, elementLength, indexBuf.getFormat(), indexData); + curOffset += elementLength; + } + } + else { + drawElements(mode, indexData.capacity(), indexBuf.getFormat(), indexData); + } + } + + protected abstract void drawElements(Mode mode, int count, Format format, Buffer indices); + + protected abstract void drawRangeElements(Mode mode, int start, int end, int count, + Format format, long indices_buffer_offset); + + protected abstract void drawElementsInstanced(Mode mode, int indices_count, Format format, + long indices_buffer_offset, int primcount); + + protected abstract void bindElementArrayBuffer(int buffer); + + protected int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return Helper.Filter.LINEAR.getGLConstant(); + case Nearest: + return Helper.Filter.NEAREST.getGLConstant(); + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + public void drawTriangleListVBO(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) { + bindElementArrayBuffer(bufId); + context.boundElementArrayVBO = bufId; + } + + 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(); + + Mode elMode = Mode.Triangles; + 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 = Mode.TriangleStrip; + } + else if (i == fanStart) { + elMode = Mode.TriangleStrip; + } + int elementLength = elementLengths[i]; + + if (useInstancing) { + drawElementsInstanced(elMode, elementLength, indexBuf.getFormat(), curOffset, + count); + } + else { + drawRangeElements(elMode, 0, vertCount, elementLength, indexBuf.getFormat(), + curOffset); + } + + curOffset += elementLength * elSize; + } + } + else { + if (useInstancing) { + drawElementsInstanced(mesh.getMode(), indexBuf.getData().capacity(), + indexBuf.getFormat(), 0, count); + } + else { + drawRangeElements(mesh.getMode(), 0, vertCount, indexBuf.getData().capacity(), + indexBuf.getFormat(), 0); + } + } + } + + protected abstract int convertVertexFormat(VertexBuffer.Format fmt); + + protected abstract int convertFormat(Format format); + + protected abstract int convertElementMode(Mesh.Mode mode); + + protected abstract void drawArrays(Mesh mesh); + + protected abstract void deleteFramebuffer(); + + protected abstract void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb); + + protected abstract void bindFramebuffer(int framebuffer); + + public void deleteFrameBuffer(FrameBuffer fb) { + if (fb.getId() != -1) { + if (context.boundFBO == fb.getId()) { + bindFramebuffer(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()); + deleteFramebuffer(); + fb.resetObject(); + + statistics.onDeleteFrameBuffer(); + } + } + + protected int getUniformLocation(Shader shader) { + return helper.getUniformLocation(shader, stringBuf.toString(),nameBuf); + } + + protected void updateUniformLocation(Shader shader, Uniform uniform) { + if (glslVer != -1) { + stringBuf.setLength(0); + stringBuf.append(uniform.getName()).append('\0'); + updateNameBuffer(); + int loc = getUniformLocation(shader); + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + logger.log(Level.WARNING, "Uniform {0} is not declared in shader.", + uniform.getName()); + } + else { + uniform.setLocation(loc); + } + } + } + + protected abstract void updateUniformVar(Shader shader, Uniform uniform); + + protected abstract void deleteShader(ShaderSource source); + + public void deleteShaderSource(ShaderSource source) { + if (glslVer != -1) { + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + source.setUsable(false); + source.clearUpdateNeeded(); + deleteShader(source); + source.resetObject(); + } + } + + protected abstract boolean isShaderValid(Shader shader); + + public void setShader(Shader shader) { + if (glslVer != -1) { + if (shader == null) { + if (context.boundShaderProgram > 0) { + helper.useProgram(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); + if (context.boundShaderProgram != shader.getId()) { + if (VALIDATE_SHADER) { + // check if shader can be used + // with current state + boolean validateOK = isShaderValid(shader); + if (validateOK) { + logger.fine("shader validate success"); + } + else { + logger.warning("shader validate failure"); + } + } + + helper.useProgram(shader.getId()); + statistics.onShaderUse(shader, true); + context.boundShaderProgram = shader.getId(); + boundShader = shader; + } + else { + statistics.onShaderUse(shader, false); + } + } + } + } + + public abstract void updateShaderData(Shader shader); + + protected abstract void bindDrawFramebuffer(int framebuffer); + + protected abstract void bindReadFramebuffer(int framebuffer); + + protected abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, + int dstY0, int dstX1, int dstY1, int mask, int filter); + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + if (framebufferBlit) { + 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) { + bindReadFramebuffer(0); + // srcW = viewWidth; + // srcH = viewHeight; + } + else { + bindReadFramebuffer(src.getId()); + srcW = src.getWidth(); + srcH = src.getHeight(); + } + if (dst == null) { + bindDrawFramebuffer(0); + // dstW = viewWidth; + // dstH = viewHeight; + } + else { + bindDrawFramebuffer(dst.getId()); + dstW = dst.getWidth(); + dstH = dst.getHeight(); + } + blitFramebuffer(0, 0, srcW, srcH, 0, 0, dstW, dstH, Helper.BufferBit.COLOR_BUFFER.getGLConstant() + | Helper.BufferBit.DEPTH_BUFFER.getGLConstant(), Helper.Filter.NEAREST.getGLConstant()); + + bindFramebuffer(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? + } + } + + protected abstract void checkFrameBufferError(); + + protected abstract int genFramebufferId(); + + public void updateFrameBuffer(FrameBuffer fb) { + int id = fb.getId(); + if (id == -1) { + // create FBO + id = genFramebufferId(); + fb.setId(id); + objManager.registerForCleanup(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id) { + bindFramebuffer(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 abstract void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb); + + protected abstract void deleteTexture(); + + public void deleteTexture(Texture tex) { + int texId = tex.getId(); + if (texId != -1) { + intBuf1.put(0, texId); + intBuf1.position(0).limit(1); + deleteTexture(); + tex.resetObject(); + } + } + + protected abstract int convertTextureType(Texture.Type type); + + public void clearTextureUnits() { + IDList textureList = context.textureIndexList; + Texture[] textures = context.boundTextures; + for (int i = 0; i < textureList.oldLen; i++) { + int idx = textureList.oldList[i]; + + if (context.boundTextureUnit != idx) { + setActiveTexture(Helper.TEXTURE0 + idx); + context.boundTextureUnit = idx; + } + disable(convertTextureType(textures[idx].getType())); + textures[idx] = null; + } + context.textureIndexList.copyNewToOld(); + } + + protected abstract void enable(int cap); + + protected abstract void disable(int cap); + + protected abstract void setActiveTexture(int unit); + + protected abstract void bindTexture(int type,int id); + + public void clearClipRect() { + if (context.clipRectEnabled) { + disable(Helper.TEXTURE0); + context.clipRectEnabled = false; + } + } + + protected abstract void disableClientState(int cap); + + public void clearVertexAttribs() { + for (int i = 0; i < 16; i++) { + VertexBuffer vb = context.boundAttribs[i]; + if (vb != null) { + int arrayType = convertArrayType(vb.getBufferType()); + disableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = null; + } + } + } + + protected abstract int convertArrayType(VertexBuffer.Type type); + + protected abstract void deleteBuffer(); + + public void deleteBuffer(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + intBuf1.put(0, bufId); + intBuf1.position(0).limit(1); + deleteBuffer(); + vb.resetObject(); + } + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + if (glslVer == -1) { + this.viewMatrix.set(viewMatrix); + this.projMatrix.set(projMatrix); + if (context.matrixMode != Helper.MatrixMode.PROJECTION.getGLConstant()) { + helper.setMatrixMode(Helper.MatrixMode.PROJECTION); + context.matrixMode = Helper.MatrixMode.PROJECTION.getGLConstant(); + } + helper.loadMatrixf(storeMatrix(projMatrix, fb16)); + } + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + if (glslVer == -1) { + this.worldMatrix.set(worldMatrix); + if (context.matrixMode != Helper.MatrixMode.MODELVIEW.getGLConstant()) { + helper.setMatrixMode(Helper.MatrixMode.MODELVIEW); + context.matrixMode = Helper.MatrixMode.MODELVIEW.getGLConstant(); + } + helper.loadMatrixf(storeMatrix(viewMatrix, fb16)); + helper.multMatrixf(storeMatrix(worldMatrix, fb16)); + } + } + + public void setViewPort(int x, int y, int width, int height) { + helper.setViewPort(x, y, width, height); + vpX = x; + vpY = y; + vpW = width; + vpH = height; + } + + public void setDepthRange(float start, float end) { + helper.setDepthRange(start, end); + } +} diff --git a/engine/src/jogl2/com/jme3/renderer/jogl/JoglRenderer.java b/engine/src/jogl2/com/jme3/renderer/jogl/JoglRenderer.java new file mode 100644 index 000000000..2763e0452 --- /dev/null +++ b/engine/src/jogl2/com/jme3/renderer/jogl/JoglRenderer.java @@ -0,0 +1,1743 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.jogl; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.logging.Level; + +import javax.media.opengl.GL; +import javax.media.opengl.GL2; +import javax.media.opengl.GL2ES1; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GL2GL3; +import javax.media.opengl.GLContext; +import javax.media.opengl.fixedfunc.GLLightingFunc; +import javax.media.opengl.fixedfunc.GLPointerFunc; + +import com.jme3.glhelper.Helper; +import com.jme3.glhelper.jogl.JoglHelper; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.AbstractRenderer; +import com.jme3.renderer.Caps; +import com.jme3.renderer.IDList; +import com.jme3.renderer.RendererException; +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; + +/** + * OpenGL renderer that relies on a specific OpenGL binding. It handles operations that can hardly be + * done in a binding-agnostic way (detection of available extensions, capabilities, manipulation of + * shader sources, ...) + * + */ +public class JoglRenderer extends AbstractRenderer { + + public JoglRenderer() { + } + + public void initialize() { + helper = new JoglHelper(); + GL gl = GLContext.getCurrentGL(); + logger.log(Level.INFO, "Vendor: {0}", gl.glGetString(GL.GL_VENDOR)); + logger.log(Level.INFO, "Renderer: {0}", gl.glGetString(GL.GL_RENDERER)); + logger.log(Level.INFO, "Version: {0}", gl.glGetString(GL.GL_VERSION)); + + applyRenderState(RenderState.DEFAULT); + + powerOf2 = gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two"); + hardwareMips = gl.isExtensionAvailable("GL_SGIS_generate_mipmap"); + vbo = gl.isExtensionAvailable("GL_ARB_vertex_buffer_object"); + + // TODO: use gl.glGetString(GL.GL_VERSION) + /*if (ctxCaps.OpenGL20) { + caps.add(Caps.OpenGL20); + } + if (ctxCaps.OpenGL21) { + caps.add(Caps.OpenGL21); + } + if (ctxCaps.OpenGL30) { + caps.add(Caps.OpenGL30); + }*/ + + final boolean glslSupported = gl.isExtensionAvailable("GL_ARB_shader_objects") + && gl.isExtensionAvailable("GL_ARB_fragment_shader") && gl.isExtensionAvailable("GL_ARB_vertex_shader") + && gl.isExtensionAvailable("GL_ARB_shading_language_100"); + String versionStr = glslSupported?gl.glGetString(GL2ES2.GL_SHADING_LANGUAGE_VERSION):null; + if (versionStr == null || versionStr.equals("")) { + glslVer = -1; + // no, I need the support of low end graphics cards too + } + else { + 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; + } + // N.B: do NOT force GLSL100 support + } + + gl.glGetIntegerv(GL2GL3.GL_DRAW_BUFFER, intBuf1); + initialDrawBuf = intBuf1.get(0); + gl.glGetIntegerv(GL2GL3.GL_READ_BUFFER, intBuf1); + initialReadBuf = intBuf1.get(0); + + gl.glGetIntegerv(GL2ES2.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); + } + + gl.glGetIntegerv(GL2ES2.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); + fragTextureUnits = intBuf16.get(0); + logger.log(Level.FINER, "Texture Units: {0}", fragTextureUnits); + + gl.glGetIntegerv(GL2GL3.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16); + vertexUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); + + gl.glGetIntegerv(GL2GL3.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); + fragUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); + + gl.glGetIntegerv(GL2ES2.GL_MAX_VERTEX_ATTRIBS, intBuf16); + vertexAttribs = intBuf16.get(0); + logger.log(Level.FINER, "Vertex Attributes: {0}", vertexAttribs); + + gl.glGetIntegerv(GL2GL3.GL_MAX_VARYING_FLOATS, intBuf16); + int varyingFloats = intBuf16.get(0); + logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); + + gl.glGetIntegerv(GL.GL_SUBPIXEL_BITS, intBuf16); + int subpixelBits = intBuf16.get(0); + logger.log(Level.FINER, "Subpixel Bits: {0}", subpixelBits); + + gl.glGetIntegerv(GL2GL3.GL_MAX_ELEMENTS_VERTICES, intBuf16); + maxVertCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); + + gl.glGetIntegerv(GL2GL3.GL_MAX_ELEMENTS_INDICES, intBuf16); + maxTriCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); + + gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, intBuf16); + maxTexSize = intBuf16.get(0); + logger.log(Level.FINER, "Maximum Texture Resolution: {0}", maxTexSize); + + gl.glGetIntegerv(GL.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); + maxCubeTexSize = intBuf16.get(0); + logger.log(Level.FINER, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); + + if (gl.isExtensionAvailable("GL_ARB_color_buffer_float")) { + // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. + if (gl.isExtensionAvailable("GL_ARB_half_float_pixel")) { + caps.add(Caps.FloatColorBuffer); + } + } + + if (gl.isExtensionAvailable("GL_ARB_depth_buffer_float")) { + caps.add(Caps.FloatDepthBuffer); + } + + if (gl.isExtensionAvailable("GL_ARB_draw_instanced")) { + caps.add(Caps.MeshInstancing); + } + + if (gl.isExtensionAvailable("GL_ARB_fragment_program")) { + caps.add(Caps.ARBprogram); + } + + if (gl.isExtensionAvailable("GL_ARB_texture_buffer_object")) { + caps.add(Caps.TextureBuffer); + } + + if (gl.isExtensionAvailable("GL_ARB_texture_float")) { + if (gl.isExtensionAvailable("GL_ARB_half_float_pixel")) { + caps.add(Caps.FloatTexture); + } + } + + if (gl.isExtensionAvailable("GL_ARB_vertex_array_object")) { + caps.add(Caps.VertexBufferArray); + } + + boolean latc = gl.isExtensionAvailable("GL_EXT_texture_compression_latc"); + boolean atdc = gl.isExtensionAvailable("GL_ATI_texture_compression_3dc"); + if (latc || atdc) { + caps.add(Caps.TextureCompressionLATC); + if (atdc && !latc) { + tdc = true; + } + } + + if (gl.isExtensionAvailable("GL_EXT_packed_float")) { + caps.add(Caps.PackedFloatColorBuffer); + if (gl.isExtensionAvailable("GL_ARB_half_float_pixel")) { + // because textures are usually uploaded as RGB16F + // need half-float pixel + caps.add(Caps.PackedFloatTexture); + } + } + + if (gl.isExtensionAvailable("GL_EXT_texture_array")) { + caps.add(Caps.TextureArray); + } + + if (gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")) { + caps.add(Caps.SharedExponentTexture); + } + + if (gl.isExtensionAvailable("GL_EXT_framebuffer_object")) { + caps.add(Caps.FrameBuffer); + + gl.glGetIntegerv(GL.GL_MAX_RENDERBUFFER_SIZE, intBuf16); + maxRBSize = intBuf16.get(0); + logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); + + gl.glGetIntegerv(GL2GL3.GL_MAX_COLOR_ATTACHMENTS, intBuf16); + maxFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs); + + if (gl.isExtensionAvailable("GL_EXT_framebuffer_multisample")) { + caps.add(Caps.FrameBufferMultisample); + + gl.glGetIntegerv(GL2GL3.GL_MAX_SAMPLES, intBuf16); + maxFBOSamples = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples); + } + + if (gl.isExtensionAvailable("GL_ARB_draw_buffers")) { + caps.add(Caps.FrameBufferMRT); + gl.glGetIntegerv(GL2GL3.GL_MAX_DRAW_BUFFERS, intBuf16); + maxMRTFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); + } + } + + if (gl.isExtensionAvailable("GL_ARB_multisample")) { + gl.glGetIntegerv(GL.GL_SAMPLE_BUFFERS, intBuf16); + boolean available = intBuf16.get(0) != 0; + gl.glGetIntegerv(GL.GL_SAMPLES, intBuf16); + int samples = intBuf16.get(0); + logger.log(Level.FINER, "Samples: {0}", samples); + boolean enabled = gl.glIsEnabled(GL.GL_MULTISAMPLE); + if (samples > 0 && available && !enabled) { + gl.glEnable(GL.GL_MULTISAMPLE); + } + } + framebufferBlit = gl.isExtensionAvailable("GL_EXT_framebuffer_blit"); + renderbufferStorageMultisample = gl.isExtensionAvailable("GL_EXT_framebuffer_multisample") + && gl.isFunctionAvailable("glRenderbufferStorageMultisample"); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + bits = Helper.BufferBit.COLOR_BUFFER.getGLConstant(); + } + if (depth) { + bits |= Helper.BufferBit.DEPTH_BUFFER.getGLConstant(); + } + if (stencil) { + bits |= Helper.BufferBit.STENCIL_BUFFER.getGLConstant(); + } + if (bits != 0) { + //TODO: use helper.clear(BufferBit bufferBit) + GLContext.getCurrentGL().glClear(bits); + } + } + + public void applyRenderState(RenderState state) { + GL gl = GLContext.getCurrentGL(); + if (state.isWireframe() && !context.wireframe) { + gl.getGL2().glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE); + context.wireframe = true; + } + else if (!state.isWireframe() && context.wireframe) { + gl.getGL2().glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL); + context.wireframe = false; + } + if (state.isDepthTest() && !context.depthTestEnabled) { + gl.glEnable(GL.GL_DEPTH_TEST); + gl.glDepthFunc(GL.GL_LEQUAL); + context.depthTestEnabled = true; + } + else if (!state.isDepthTest() && context.depthTestEnabled) { + gl.glDisable(GL.GL_DEPTH_TEST); + context.depthTestEnabled = false; + } + if (state.isAlphaTest() && !context.alphaTestEnabled) { + gl.glEnable(GL2ES1.GL_ALPHA_TEST); + gl.getGL2().glAlphaFunc(GL.GL_GREATER, state.getAlphaFallOff()); + context.alphaTestEnabled = true; + } + else if (!state.isAlphaTest() && context.alphaTestEnabled) { + gl.glDisable(GL2ES1.GL_ALPHA_TEST); + context.alphaTestEnabled = false; + } + if (state.isDepthWrite() && !context.depthWriteEnabled) { + gl.glDepthMask(true); + context.depthWriteEnabled = true; + } + else if (!state.isDepthWrite() && context.depthWriteEnabled) { + gl.glDepthMask(false); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled) { + gl.glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } + else if (!state.isColorWrite() && context.colorWriteEnabled) { + gl.glColorMask(false, false, false, false); + context.colorWriteEnabled = false; + } + if (state.isPolyOffset()) { + if (!context.polyOffsetEnabled) { + gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); + gl.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) { + gl.glPolygonOffset(state.getPolyOffsetFactor(), state.getPolyOffsetUnits()); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } + else { + if (context.polyOffsetEnabled) { + disable(GL.GL_POLYGON_OFFSET_FILL); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + disable(GL.GL_CULL_FACE); + } + else { + enable(GL.GL_CULL_FACE); + } + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + gl.glCullFace(GL.GL_BACK); + break; + case Front: + gl.glCullFace(GL.GL_FRONT); + break; + case FrontAndBack: + gl.glCullFace(GL.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) { + disable(GL.GL_BLEND); + } + else { + enable(GL.GL_BLEND); + } + + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE); + break; + case AlphaAdditive: + gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); + break; + case Alpha: + gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); + break; + case Color: + gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); + break; + case PremultAlpha: + gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_ZERO); + break; + case ModulateX2: + gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + + context.blendMode = state.getBlendMode(); + } + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + enable(GL.GL_SCISSOR_TEST); + context.clipRectEnabled = true; + } + helper.setScissor(x, y, width, height); + } + + @Override + protected void updateUniformVar(Shader shader, Uniform uniform) { + 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; + GL gl = GLContext.getCurrentGL(); + switch (uniform.getVarType()) { + case Float: + Float f = (Float) uniform.getValue(); + gl.getGL2().glUniform1f(loc, f.floatValue()); + break; + case Vector2: + Vector2f v2 = (Vector2f) uniform.getValue(); + gl.getGL2().glUniform2f(loc, v2.getX(), v2.getY()); + break; + case Vector3: + Vector3f v3 = (Vector3f) uniform.getValue(); + gl.getGL2().glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); + break; + case Vector4: + Object val = uniform.getValue(); + if (val instanceof ColorRGBA) { + ColorRGBA c = (ColorRGBA) val; + gl.getGL2().glUniform4f(loc, c.r, c.g, c.b, c.a); + } + else { + Quaternion c = (Quaternion) uniform.getValue(); + gl.getGL2().glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); + } + break; + case Boolean: + Boolean b = (Boolean) uniform.getValue(); + gl.getGL2().glUniform1i(loc, b.booleanValue() ? GL.GL_TRUE : GL.GL_FALSE); + break; + case Matrix3: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 9; + gl.getGL2().glUniformMatrix3fv(loc, 1, false, fb); + break; + case Matrix4: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 16; + gl.getGL2().glUniformMatrix4fv(loc, 1, false, fb); + break; + case FloatArray: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2().glUniform1fv(loc, 1, fb); + break; + case Vector2Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2().glUniform2fv(loc, 1, fb); + break; + case Vector3Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2().glUniform3fv(loc, 1, fb); + break; + case Vector4Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2().glUniform4fv(loc, 1, fb); + break; + case Matrix4Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2().glUniformMatrix4fv(loc, 1, false, fb); + break; + case Int: + Integer i = (Integer) uniform.getValue(); + gl.getGL2().glUniform1i(loc, i.intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + + uniform.getVarType()); + } + } + + public void setLighting(LightList list) { + if (glslVer == -1) { + GL gl = GLContext.getCurrentGL(); + if (list == null || list.size() == 0) { + // turn off lighting + gl.glDisable(GLLightingFunc.GL_LIGHTING); + return; + } + + gl.glEnable(GLLightingFunc.GL_LIGHTING); + gl.getGL2().glShadeModel(GLLightingFunc.GL_SMOOTH); + + float[] temp = new float[4]; + + // reset model view to specify + // light positions in world space + // instead of model space + // gl.glPushMatrix(); + // gl.glLoadIdentity(); + + for (int i = 0; i < list.size() + 1; i++) { + + int lightId = GLLightingFunc.GL_LIGHT0 + i; + + if (list.size() <= i) { + // goes beyond the num lights we need + // disable it + gl.glDisable(lightId); + break; + } + + Light l = list.get(i); + + if (!l.isEnabled()) { + gl.glDisable(lightId); + continue; + } + + ColorRGBA color = l.getColor(); + color.toArray(temp); + + gl.glEnable(lightId); + gl.getGL2().glLightfv(lightId, GLLightingFunc.GL_DIFFUSE, temp, 0); + gl.getGL2().glLightfv(lightId, GLLightingFunc.GL_SPECULAR, temp, 0); + + ColorRGBA.Black.toArray(temp); + gl.getGL2().glLightfv(lightId, GLLightingFunc.GL_AMBIENT, temp, 0); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + dl.getDirection().toArray(temp); + temp[3] = 0f; // marks to GL its a directional light + gl.getGL2().glLightfv(lightId, GLLightingFunc.GL_POSITION, temp, 0); + break; + case Point: + PointLight pl = (PointLight) l; + pl.getPosition().toArray(temp); + temp[3] = 1f; // marks to GL its a point light + gl.getGL2().glLightfv(lightId, GLLightingFunc.GL_POSITION, temp, 0); + break; + } + + } + // restore modelview to original value + // gl.glPopMatrix(); + } + } + + public int convertShaderType(ShaderType type) { + switch (type) { + case Fragment: + return GL2ES2.GL_FRAGMENT_SHADER; + case Vertex: + return GL2ES2.GL_VERTEX_SHADER; + // case Geometry: + // return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; + default: + throw new RuntimeException("Unrecognized shader type."); + } + } + + @Override + protected void deleteShader(ShaderSource source) { + GL gl = GLContext.getCurrentGL(); + gl.getGL2().glDeleteShader(source.getId()); + } + + @Override + public void updateShaderData(Shader shader) { + if (glslVer != -1) { + GL gl = GLContext.getCurrentGL(); + int id = shader.getId(); + boolean needRegister = false; + if (id == -1) { + // create program + id = gl.getGL2().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; + } + gl.getGL2().glAttachShader(id, source.getId()); + } + // link shaders to program + gl.getGL2().glLinkProgram(id); + + gl.getGL2().glGetProgramiv(id, GL2ES2.GL_LINK_STATUS, intBuf1); + boolean linkOK = intBuf1.get(0) == GL.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !linkOK) { + gl.getGL2().glGetProgramiv(id, GL2ES2.GL_INFO_LOG_LENGTH, intBuf1); + int length = intBuf1.get(0); + if (length > 3) { + // get infos + ByteBuffer logBuf = BufferUtils.createByteBuffer(length); + gl.getGL2().glGetProgramInfoLog(id, logBuf.limit(), intBuf1, 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) { + logger.log(Level.WARNING, "shader link failure. \n{0}", infoLog); + } + else { + logger.warning("shader link failure"); + } + } + + 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 updateShaderSourceData(ShaderSource source, String language) { + if (glslVer != -1) { + GL gl = GLContext.getCurrentGL(); + int id = source.getId(); + if (id == -1) { + // create id + id = gl.getGL2().glCreateShader(convertShaderType(source.getType())); + 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(); + + final byte[] array = new byte[codeBuf.limit()]; + codeBuf.get(array); + gl.getGL2().glShaderSourceARB(id, 1, new String[] { new String(array) }, + new int[] { array.length }, 0); + gl.getGL2().glCompileShader(id); + + gl.getGL2().glGetShaderiv(id, GL2ES2.GL_COMPILE_STATUS, intBuf1); + + boolean compiledOK = intBuf1.get(0) == GL.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !compiledOK) { + // even if compile succeeded, check + // log for warnings + gl.getGL2().glGetShaderiv(id, GL2ES2.GL_INFO_LOG_LENGTH, intBuf1); + int length = intBuf1.get(0); + if (length > 3) { + // get infos + ByteBuffer logBuf = BufferUtils.createByteBuffer(length); + gl.getGL2().glGetShaderInfoLog(id, logBuf.limit(), intBuf1, 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 { + if (infoLog != null) { + logger.log(Level.WARNING, "{0} compile error: {1}", + new Object[] { source.getName(), infoLog }); + } + else { + logger.log(Level.WARNING, "{0} compile error: ?", source.getName()); + } + logger.log(Level.WARNING, "{0}{1}", + new Object[] { source.getDefines(), source.getSource() }); + } + + 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. + gl.getGL2().glDeleteShader(id); + } + else { + // register for cleanup since the ID is usable + objManager.registerForCleanup(source); + } + } + } + + @Override + protected boolean isShaderValid(Shader shader) { + GL gl = GLContext.getCurrentGL(); + gl.getGL2().glValidateProgram(shader.getId()); + gl.getGL2().glGetProgramiv(shader.getId(), GL2ES2.GL_VALIDATE_STATUS, intBuf1); + return intBuf1.get(0) == GL.GL_TRUE; + } + + public void deleteShader(Shader shader) { + if (glslVer != -1) { + if (shader.getId() == -1) { + logger.warning("Shader is not uploaded to GPU, cannot delete."); + return; + } + GL gl = GLContext.getCurrentGL(); + for (ShaderSource source : shader.getSources()) { + if (source.getId() != -1) { + gl.getGL2().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(); + gl.getGL2().glDeleteProgram(shader.getId()); + + statistics.onDeleteShader(); + } + } + + @Override + protected void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, + int dstY0, int dstX1, int dstY1, int mask, int filter) { + GL gl = GLContext.getCurrentGL(); + gl.getGL2().glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, + filter); + } + + public void setFrameBuffer(FrameBuffer fb) { + if (lastFb == fb) { + return; + } + + GL gl = GLContext.getCurrentGL(); + if (fb == null) { + // unbind any fbos + if (context.boundFBO != 0) { + bindFramebuffer(0); + statistics.onFrameBufferUse(null, true); + + context.boundFBO = 0; + } + // select back buffer + if (context.boundDrawBuf != -1) { + gl.getGL2().glDrawBuffer(initialDrawBuf); + context.boundDrawBuf = -1; + } + if (context.boundReadBuf != -1) { + gl.getGL2().glReadBuffer(initialReadBuf); + context.boundReadBuf = -1; + } + + lastFb = null; + } + else { + if (fb.isUpdateNeeded()) { + updateFrameBuffer(fb); + } + + if (context.boundFBO != fb.getId()) { + bindFramebuffer(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) { + gl.getGL2().glDrawBuffer(GL.GL_NONE); + context.boundDrawBuf = -2; + } + if (context.boundReadBuf != -2) { + gl.getGL2().glReadBuffer(GL.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.GL_COLOR_ATTACHMENT0 + i); + } + + intBuf16.flip(); + gl.getGL2().glDrawBuffers(intBuf16.limit(), intBuf16); + context.boundDrawBuf = 100 + fb.getNumColorBuffers(); + } + } + else { + RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); + // select this draw buffer + if (context.boundDrawBuf != rb.getSlot()) { + gl.getGL2().glDrawBuffer(GL.GL_COLOR_ATTACHMENT0 + 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; + } + } + + @Override + protected int genFramebufferId() { + GL gl = GLContext.getCurrentGL(); + gl.glGenFramebuffers(1, intBuf1); + return intBuf1.get(0); + } + + private int convertAttachmentSlot(int attachmentSlot) { + // can also add support for stencil here + if (attachmentSlot == -100) { + return GL.GL_DEPTH_ATTACHMENT; + } + else if (attachmentSlot < 0 || attachmentSlot >= 16) { + throw new UnsupportedOperationException("Invalid FBO attachment slot: " + + attachmentSlot); + } + + return GL.GL_COLOR_ATTACHMENT0 + attachmentSlot; + } + + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { + GL gl = GLContext.getCurrentGL(); + Texture tex = rb.getTexture(); + if (tex.isUpdateNeeded()) { + updateTextureData(tex); + } + + gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType()), tex.getId(), 0); + } + + @Override + 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) { + GL gl = GLContext.getCurrentGL(); + gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, convertAttachmentSlot(rb.getSlot()), + GL.GL_RENDERBUFFER, rb.getId()); + } + } + + @Override + protected void checkFrameBufferError() { + GL gl = GLContext.getCurrentGL(); + int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER); + switch (status) { + case GL.GL_FRAMEBUFFER_COMPLETE: + break; + case GL.GL_FRAMEBUFFER_UNSUPPORTED: + // Choose different formats + throw new IllegalStateException("Framebuffer object format is " + + "unsupported by the video hardware."); + case GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw new IllegalStateException("Framebuffer has erronous attachment."); + case GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw new IllegalStateException("Framebuffer is missing required attachment."); + case GL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + throw new IllegalStateException( + "Framebuffer attachments must have same dimensions."); + case GL.GL_FRAMEBUFFER_INCOMPLETE_FORMATS: + throw new IllegalStateException("Framebuffer attachments must have same formats."); + case GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + throw new IllegalStateException("Incomplete draw buffer."); + case GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + throw new IllegalStateException("Incomplete read buffer."); + case GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + 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) { + GL gl = GLContext.getCurrentGL(); + int id = rb.getId(); + if (id == -1) { + gl.glGenRenderbuffers(1, intBuf1); + id = intBuf1.get(0); + rb.setId(id); + } + + if (context.boundRB != id) { + gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 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 && renderbufferStorageMultisample) { + int samples = fb.getSamples(); + if (maxFBOSamples < samples) { + samples = maxFBOSamples; + } + gl.getGL2() + .glRenderbufferStorageMultisample(GL.GL_RENDERBUFFER, samples, + TextureUtil.convertTextureFormat(rb.getFormat()), fb.getWidth(), + fb.getHeight()); + } + else { + gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, + TextureUtil.convertTextureFormat(rb.getFormat()), fb.getWidth(), fb.getHeight()); + } + } + + @Override + protected void deleteFramebuffer() { + GL gl = GLContext.getCurrentGL(); + gl.getGL2().glDeleteFramebuffers(1, intBuf1); + } + + @Override + protected void bindFramebuffer(int framebuffer) { + GL gl = GLContext.getCurrentGL(); + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, framebuffer); + } + + @Override + protected void bindDrawFramebuffer(int framebuffer) { + GL gl = GLContext.getCurrentGL(); + gl.glBindFramebuffer(GL2GL3.GL_DRAW_FRAMEBUFFER, framebuffer); + } + + @Override + protected void bindReadFramebuffer(int framebuffer) { + GL gl = GLContext.getCurrentGL(); + gl.glBindFramebuffer(GL2GL3.GL_READ_FRAMEBUFFER, framebuffer); + } + + @Override + protected void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + intBuf1.put(0, rb.getId()); + GL gl = GLContext.getCurrentGL(); + gl.getGL2().glDeleteRenderbuffers(1, intBuf1); + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + GL gl = GLContext.getCurrentGL(); + 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()) { + gl.getGL2().glReadBuffer(GL.GL_COLOR_ATTACHMENT0 + rb.getSlot()); + context.boundReadBuf = rb.getSlot(); + } + } + else { + setFrameBuffer(null); + } + gl.glReadPixels(vpX, vpY, vpW, vpH, GL2GL3.GL_BGRA, GL.GL_UNSIGNED_BYTE, byteBuf); + } + + @Override + protected int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return GL.GL_TEXTURE_2D; + case TwoDimensionalArray: + return GL.GL_TEXTURE_2D_ARRAY; + case ThreeDimensional: + return GL2GL3.GL_TEXTURE_3D; + case CubeMap: + return GL.GL_TEXTURE_CUBE_MAP; + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return GL.GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return GL.GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return GL.GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return GL.GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return GL.GL_LINEAR; + case NearestNoMipMaps: + return GL.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { + case BorderClamp: + return GL2GL3.GL_CLAMP_TO_BORDER; + case Clamp: + return GL2.GL_CLAMP; + case EdgeClamp: + return GL.GL_CLAMP_TO_EDGE; + case Repeat: + return GL.GL_REPEAT; + case MirroredRepeat: + return GL.GL_MIRRORED_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: " + mode); + } + } + + public void updateTextureData(Texture tex) { + int texId = tex.getId(); + GL gl = GLContext.getCurrentGL(); + if (texId == -1) { + // create texture + gl.glGenTextures(1, intBuf1); + texId = intBuf1.get(0); + tex.setId(texId); + objManager.registerForCleanup(tex); + } + + // bind texture + int target = convertTextureType(tex.getType()); + if (context.boundTextures[0] != tex) { + if (context.boundTextureUnit != 0) { + setActiveTexture(GL.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + + bindTexture(target, texId); + context.boundTextures[0] = tex; + } + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + gl.glTexParameteri(target, GL.GL_TEXTURE_MIN_FILTER, minFilter); + gl.glTexParameteri(target, GL.GL_TEXTURE_MAG_FILTER, magFilter); + + // repeat modes + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: + gl.glTexParameteri(target, GL2GL3.GL_TEXTURE_WRAP_R, + convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + gl.glTexParameteri(target, GL.GL_TEXTURE_WRAP_T, + convertWrapMode(tex.getWrap(WrapAxis.T))); + // fall down here is intentional.. + // case OneDimensional: + gl.glTexParameteri(target, GL.GL_TEXTURE_WRAP_S, + convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); + } + + Image img = tex.getImage(); + if (img != null) { + boolean generateMips = false; + if (!img.hasMipmaps() && tex.getMinFilter().usesMipMapLevels()) { + // No pregenerated mips available, + // generate from base level if required + if (hardwareMips) { + gl.glTexParameteri(target, GL2ES1.GL_GENERATE_MIPMAP, GL.GL_TRUE); + } + else { + generateMips = true; + } + } + + TextureUtil.uploadTexture(gl, img, tex.getImageDataIndex(), generateMips, powerOf2); + } + + tex.clearUpdateNeeded(); + } + + private void checkTexturingUsed() { + IDList textureList = context.textureIndexList; + GL gl = GLContext.getCurrentGL(); + // old mesh used texturing, new mesh doesn't use it + // should actually go through entire oldLen and + // disable texturing for each unit.. but that's for later. + if (textureList.oldLen > 0 && textureList.newLen == 0) { + gl.glDisable(GL.GL_TEXTURE_2D); + } + } + + public void setTexture(int unit, Texture tex) { + if (tex.isUpdateNeeded()) { + updateTextureData(tex); + } + + int texId = tex.getId(); + assert texId != -1; + + Texture[] textures = context.boundTextures; + + int type = convertTextureType(tex.getType()); + if (!context.textureIndexList.moveToNew(unit)) { + if (context.boundTextureUnit != unit) { + setActiveTexture(GL.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + enable(type); + } + + if (textures[unit] != tex) { + if (context.boundTextureUnit != unit) { + setActiveTexture(GL.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + bindTexture(type, texId); + textures[unit] = tex; + } + } + + @Override + protected void enable(int cap){ + GLContext.getCurrentGL().glEnable(cap); + } + + @Override + protected void disable(int cap){ + GLContext.getCurrentGL().glDisable(cap); + } + + @Override + protected void setActiveTexture(int unit){ + GLContext.getCurrentGL().glActiveTexture(unit); + } + + @Override + protected void bindTexture(int type,int id){ + GLContext.getCurrentGL().glBindTexture(type, id); + } + + protected void deleteTexture(){ + GL gl = GLContext.getCurrentGL(); + gl.glDeleteTextures(1, intBuf1); + } + + private int convertUsage(Usage usage) { + switch (usage) { + case Static: + return GL.GL_STATIC_DRAW; + case Dynamic: + return GL.GL_DYNAMIC_DRAW; + case Stream: + return GL2ES2.GL_STREAM_DRAW; + default: + throw new RuntimeException("Unknown usage type: " + usage); + } + } + + @Override + protected int convertFormat(Format format) { + switch (format) { + case Byte: + return GL.GL_BYTE; + case UnsignedByte: + return GL.GL_UNSIGNED_BYTE; + case Short: + return GL.GL_SHORT; + case UnsignedShort: + return GL.GL_UNSIGNED_SHORT; + case Int: + return GL2ES2.GL_INT; + case UnsignedInt: + return GL2ES2.GL_UNSIGNED_INT; + case Half: + return GL.GL_HALF_FLOAT; + case Float: + return GL.GL_FLOAT; + case Double: + return GL2GL3.GL_DOUBLE; + default: + throw new RuntimeException("Unknown buffer format."); + + } + } + + public void updateBufferData(VertexBuffer vb) { + GL gl = GLContext.getCurrentGL(); + int bufId = vb.getId(); + if (bufId == -1) { + // create buffer + gl.glGenBuffers(1, intBuf1); + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerForCleanup(vb); + } + + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = GL.GL_ELEMENT_ARRAY_BUFFER; + if (context.boundElementArrayVBO != bufId) { + gl.glBindBuffer(target, bufId); + context.boundElementArrayVBO = bufId; + } + } + else { + target = GL.GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + gl.glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + Buffer data = vb.getData(); + data.rewind(); + + gl.glBufferData(target, data.capacity() * vb.getFormat().getComponentSize(), data, usage); + + vb.clearUpdateNeeded(); + } + + @Override + protected void deleteBuffer(){ + GLContext.getCurrentGL().glDeleteBuffers(1, intBuf1); + } + + @Override + protected int convertArrayType(VertexBuffer.Type type) { + switch (type) { + case Position: + return GLPointerFunc.GL_VERTEX_ARRAY; + case Normal: + return GLPointerFunc.GL_NORMAL_ARRAY; + case TexCoord: + return GLPointerFunc.GL_TEXTURE_COORD_ARRAY; + case Color: + return GLPointerFunc.GL_COLOR_ARRAY; + default: + return -1; // unsupported + } + } + + @Override + protected int convertVertexFormat(VertexBuffer.Format fmt) { + switch (fmt) { + case Byte: + return GL.GL_BYTE; + case Double: + return GL2GL3.GL_DOUBLE; + case Float: + return GL.GL_FLOAT; + case Half: + return GL.GL_HALF_FLOAT; + case Int: + return GL2ES2.GL_INT; + case Short: + return GL.GL_SHORT; + case UnsignedByte: + return GL.GL_UNSIGNED_BYTE; + case UnsignedInt: + return GL2ES2.GL_UNSIGNED_INT; + case UnsignedShort: + return GL.GL_UNSIGNED_SHORT; + default: + throw new UnsupportedOperationException("Unrecognized vertex format: " + fmt); + } + } + + @Override + protected int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return GL.GL_POINTS; + case Lines: + return GL.GL_LINES; + case LineLoop: + return GL.GL_LINE_LOOP; + case LineStrip: + return GL.GL_LINE_STRIP; + case Triangles: + return GL.GL_TRIANGLES; + case TriangleFan: + return GL.GL_TRIANGLE_FAN; + case TriangleStrip: + return GL.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + @Override + protected void setVertexAttribVBO(VertexBuffer vb, VertexBuffer idb) { + GL gl = GLContext.getCurrentGL(); + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) { + return; // unsupported + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + int bufId = idb != null ? idb.getId() : vb.getId(); + if (context.boundArrayVBO != bufId) { + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + } + + gl.getGL2().glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal) { + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled) { + gl.glEnable(GLLightingFunc.GL_NORMALIZE); + context.normalizeEnabled = true; + } + else if (!vb.isNormalized() && context.normalizeEnabled) { + gl.glDisable(GLLightingFunc.GL_NORMALIZE); + context.normalizeEnabled = false; + } + } + + int comps = vb.getNumComponents(); + int type = convertVertexFormat(vb.getFormat()); + + switch (vb.getBufferType()) { + case Position: + gl.getGL2().glVertexPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + case Normal: + gl.getGL2().glNormalPointer(type, vb.getStride(), vb.getOffset()); + break; + case Color: + gl.getGL2().glColorPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + case TexCoord: + gl.getGL2().glTexCoordPointer(comps, type, vb.getStride(), vb.getOffset()); + break; + } + } + + @Override + protected void bindElementArrayBuffer(int buffer) { + GL gl = GLContext.getCurrentGL(); + gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, buffer); + } + + @Override + 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"); + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + int programId = context.boundShaderProgram; + GL gl = GLContext.getCurrentGL(); + if (glslVer != -1 && programId > 0) { + Attribute attrib = boundShader.getAttribute(vb.getBufferType().name()); + 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 = gl.getGL2().glGetAttribLocation(programId, stringBuf.toString()); + + // 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); + } + } + + VertexBuffer[] attribs = context.boundAttribs; + if (!context.attribIndexList.moveToNew(loc)) { + gl.getGL2().glEnableVertexAttribArrayARB(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) { + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + } + + gl.getGL2().glVertexAttribPointer(loc, vb.getNumComponents(), + convertFormat(vb.getFormat()), vb.isNormalized(), vb.getStride(), + vb.getOffset()); + + attribs[loc] = vb; + } + } + else { + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) { + return; // unsupported + } + + gl.getGL2().glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal) { + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled) { + gl.glEnable(GLLightingFunc.GL_NORMALIZE); + context.normalizeEnabled = true; + } + else if (!vb.isNormalized() && context.normalizeEnabled) { + gl.glDisable(GLLightingFunc.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.clear(); + data.position(vb.getOffset()); + + switch (vb.getBufferType()) { + case Position: + gl.getGL2().glVertexPointer(comps, type, vb.getStride(), data); + break; + case Normal: + gl.getGL2().glNormalPointer(type, vb.getStride(), data); + break; + case Color: + gl.getGL2().glColorPointer(comps, type, vb.getStride(), data); + break; + case TexCoord: + gl.getGL2().glTexCoordPointer(comps, type, vb.getStride(), data); + break; + } + } + } + + @Override + protected void disableClientState(int cap){ + GLContext.getCurrentGL().getGL2().glDisableClientState(cap); + } + + @Override + protected void drawElements(Mode mode, int count, Format format, Buffer indices) { + GLContext.getCurrentGL().glDrawElements(convertElementMode(mode), count, convertVertexFormat(format), indices); + } + + @Override + protected void drawRangeElements(Mode mode, int start, int end, int count, Format format, + long indices_buffer_offset) { + GLContext.getCurrentGL().getGL2().glDrawRangeElements(convertElementMode(mode), start, end, count, + convertVertexFormat(format), indices_buffer_offset); + } + + @Override + protected void drawElementsInstanced(Mode mode, int indices_count, Format format, + long indices_buffer_offset, int primcount) { + // GL gl = GLContext.getCurrentGL(); + // FIXME: not yet in JOGL + // gl.getGL2GL3().glDrawElementsInstanced(convertElementMode(mode), indices_count, + // convertVertexFormat(format), indices_buffer_offset, primcount); + } + + @Override + protected void drawArrays(Mesh mesh) { + GLContext.getCurrentGL().glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + + private void updateDisplayList(Mesh mesh) { + GL gl = GLContext.getCurrentGL(); + if (mesh.getId() != -1) { + // delete list first + gl.getGL2().glDeleteLists(mesh.getId(), mesh.getId()); + mesh.setId(-1); + } + + // create new display list + // first set state to NULL + applyRenderState(RenderState.NULL); + + // disable lighting + setLighting(null); + + int id = gl.getGL2().glGenLists(1); + mesh.setId(id); + gl.getGL2().glNewList(id, GL2.GL_COMPILE); + renderMeshDefault(mesh, 0, 1); + gl.getGL2().glEndList(); + } + + private void renderMeshDisplayList(Mesh mesh) { + if (mesh.getId() == -1) { + updateDisplayList(mesh); + } + GLContext.getCurrentGL().getGL2().glCallList(mesh.getId()); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + GL gl = GLContext.getCurrentGL(); + if (context.pointSize != mesh.getPointSize()) { + gl.getGL2().glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + gl.glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + checkTexturingUsed(); + + if (vbo) { + renderMeshVBO(mesh, lod, count); + } + else { + boolean dynamic = false; + if (mesh.getNumLodLevels() == 0) { + IntMap bufs = mesh.getBuffers(); + for (Entry entry : bufs) { + if (entry.getValue().getUsage() != VertexBuffer.Usage.Static) { + dynamic = true; + break; + } + } + } + else { + dynamic = true; + } + + if (!dynamic) { + // dealing with a static object, generate display list + renderMeshDisplayList(mesh); + } + else { + renderMeshDefault(mesh, lod, count); + } + } + } + +} diff --git a/engine/src/jogl2/com/jme3/renderer/jogl/TextureUtil.java b/engine/src/jogl2/com/jme3/renderer/jogl/TextureUtil.java new file mode 100644 index 000000000..2aca6666d --- /dev/null +++ b/engine/src/jogl2/com/jme3/renderer/jogl/TextureUtil.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.renderer.jogl; + +import java.nio.ByteBuffer; + +import javax.media.opengl.GL; +import javax.media.opengl.GL2; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GL2GL3; + +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; + +public class TextureUtil { + + public static int convertTextureFormat(Format fmt) { + switch (fmt) { + case Alpha16: + case Alpha8: + return GL.GL_ALPHA; + case Luminance8Alpha8: + case Luminance16Alpha16: + return GL.GL_LUMINANCE_ALPHA; + case Luminance8: + case Luminance16: + return GL.GL_LUMINANCE; + case RGB10: + case RGB16: + case BGR8: + case RGB8: + case RGB565: + return GL.GL_RGB; + case RGB5A1: + case RGBA16: + case RGBA8: + return GL.GL_RGBA; + case Depth: + return GL2ES2.GL_DEPTH_COMPONENT; + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + } + + public static void uploadTexture(GL gl, Image img, int index, boolean generateMips, + boolean powerOf2) { + + 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 format = -1; + int internalFormat = -1; + int dataType = -1; + + switch (fmt) { + case Alpha16: + format = GL.GL_ALPHA; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2.GL_ALPHA16; + break; + case Alpha8: + format = GL.GL_ALPHA; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2.GL_ALPHA8; + break; + case Luminance8: + format = GL.GL_LUMINANCE; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2.GL_LUMINANCE8; + break; + case Luminance8Alpha8: + format = GL.GL_LUMINANCE_ALPHA; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2.GL_LUMINANCE8_ALPHA8; + break; + case Luminance16Alpha16: + format = GL.GL_LUMINANCE_ALPHA; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2.GL_LUMINANCE16_ALPHA16; + break; + case Luminance16: + format = GL.GL_LUMINANCE; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2.GL_LUMINANCE16; + break; + case RGB565: + format = GL.GL_RGB; + dataType = GL.GL_UNSIGNED_SHORT_5_6_5; + internalFormat = GL.GL_RGB8; + break; + case ARGB4444: + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_SHORT_4_4_4_4; + internalFormat = GL.GL_RGBA4; + break; + case RGB10: + format = GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2GL3.GL_RGB10; + break; + case RGB16: + format = GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2GL3.GL_RGB16; + break; + case RGB5A1: + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_SHORT_5_5_5_1; + internalFormat = GL.GL_RGB5_A1; + break; + case RGB8: + format = GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL.GL_RGB8; + break; + case BGR8: + format = GL2GL3.GL_BGR; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL.GL_RGB8; + break; + case RGBA16: + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL2GL3.GL_RGBA16; + break; + case RGBA8: + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_BYTE; + internalFormat = GL.GL_RGBA8; + break; + case DXT1: + compress = true; + internalFormat = GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + format = GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; + break; + case DXT1A: + compress = true; + internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_BYTE; + break; + case DXT3: + compress = true; + internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_BYTE; + break; + case DXT5: + compress = true; + internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + format = GL.GL_RGBA; + dataType = GL.GL_UNSIGNED_BYTE; + break; + case Depth: + internalFormat = GL2ES2.GL_DEPTH_COMPONENT; + format = GL2ES2.GL_DEPTH_COMPONENT; + dataType = GL.GL_UNSIGNED_BYTE; + break; + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + + if (data != null) { + gl.glPixelStorei(GL.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 }; + } + } + + 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) { + gl.glCompressedTexImage2D(GL.GL_TEXTURE_2D, i, internalFormat, mipWidth, mipHeight, + 0, data.remaining(), data); + } + else { + gl.glTexImage2D(GL.GL_TEXTURE_2D, i, internalFormat, mipWidth, mipHeight, 0, + format, dataType, data); + } + + pos += mipSizes[i]; + } + } + +} diff --git a/engine/src/jogl2/com/jme3/system/jogl/JoglAbstractDisplay.java b/engine/src/jogl2/com/jme3/system/jogl/JoglAbstractDisplay.java new file mode 100644 index 000000000..7f3a7467d --- /dev/null +++ b/engine/src/jogl2/com/jme3/system/jogl/JoglAbstractDisplay.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.system.jogl; + +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLProfile; +import javax.media.opengl.awt.GLCanvas; + +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.renderer.jogl.JoglRenderer; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.AnimatorBase; +import com.jogamp.opengl.util.FPSAnimator; + +public abstract class JoglAbstractDisplay extends JoglContext implements GLEventListener { + + private static final Logger logger = Logger.getLogger(JoglAbstractDisplay.class.getName()); + + protected GraphicsDevice device; + + protected GLCanvas canvas; + + protected AnimatorBase animator; + + protected AtomicBoolean active = new AtomicBoolean(false); + + protected boolean wasActive = false; + + protected int frameRate; + + protected boolean useAwt = true; + + protected AtomicBoolean autoFlush = new AtomicBoolean(true); + + protected boolean wasAnimating = false; + + static { + // FIXME: should be called as early as possible before any GUI task + GLProfile.initSingleton(); + } + + protected void initGLCanvas() { + device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + + GLCapabilities caps = new GLCapabilities(GLProfile.getDefault()); + caps.setHardwareAccelerated(true); + caps.setDoubleBuffered(true); + caps.setStencilBits(settings.getStencilBits()); + caps.setDepthBits(settings.getDepthBits()); + + if (settings.getSamples() > 1) { + caps.setSampleBuffers(true); + caps.setNumSamples(settings.getSamples()); + } + + canvas = new GLCanvas(caps) { + @Override + public void addNotify() { + super.addNotify(); + onCanvasAdded(); + } + + @Override + public void removeNotify() { + onCanvasRemoved(); + super.removeNotify(); + } + }; + // TODO: add a check on the settings + // set the size of the canvas as early as possible to avoid further useless reshape attempts + canvas.setSize(settings.getWidth(), settings.getHeight()); + if (settings.isVSync()) { + GLContext.getCurrentGL().setSwapInterval(1); + } + canvas.setFocusable(true); + canvas.setIgnoreRepaint(true); + canvas.addGLEventListener(this); + + // N.B: it is too early to get the GL instance from the canvas + // if (false){ + // trace mode + // jME already uses err stream, use out instead + // gl = new TraceGL(gl, System.out); + // }else if (false){ + // debug mode + // gl = new DebugGL(gl); + // }else{ + // production mode + // } + renderer = new JoglRenderer(); + } + + protected void startGLCanvas() { + if (frameRate > 0) { + animator = new FPSAnimator(canvas, frameRate); + // ((FPSAnimator)animator).setRunAsFastAsPossible(true); + } + else { + animator = new Animator(canvas); + ((Animator) animator).setRunAsFastAsPossible(true); + } + + animator.start(); + wasAnimating = true; + } + + protected void onCanvasAdded() { + } + + protected void onCanvasRemoved() { + } + + @Override + public KeyInput getKeyInput() { + return new AwtKeyInput(canvas); + } + + @Override + public MouseInput getMouseInput() { + return new AwtMouseInput(canvas); + } + + public void setAutoFlushFrames(boolean enabled) { + autoFlush.set(enabled); + } + + /** + * Callback. + */ + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { + listener.reshape(width, height); + } + + /** + * Callback. + */ + public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { + } + + /** + * Callback + */ + public void dispose(GLAutoDrawable drawable) { + + } +} diff --git a/engine/src/jogl2/com/jme3/system/jogl/JoglCanvas.java b/engine/src/jogl2/com/jme3/system/jogl/JoglCanvas.java new file mode 100644 index 000000000..c91cab8e8 --- /dev/null +++ b/engine/src/jogl2/com/jme3/system/jogl/JoglCanvas.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.system.jogl; + +import com.jme3.system.JmeCanvasContext; +import java.awt.Canvas; +import java.util.logging.Logger; +import javax.media.opengl.GLAutoDrawable; + +public class JoglCanvas extends JoglAbstractDisplay implements JmeCanvasContext { + + private static final Logger logger = Logger.getLogger(JoglCanvas.class.getName()); + private int width, height; + + public JoglCanvas(){ + super(); + initGLCanvas(); + } + + public Type getType() { + return Type.Canvas; + } + + public void setTitle(String title) { + } + + public void restart() { + } + + public void create(boolean waitFor){ + if (waitFor) + waitFor(true); + } + + public void destroy(boolean waitFor){ + if (waitFor) + waitFor(false); + } + + @Override + protected void onCanvasRemoved(){ + super.onCanvasRemoved(); + created.set(false); + waitFor(false); + } + + @Override + protected void onCanvasAdded(){ + startGLCanvas(); + } + + public void init(GLAutoDrawable drawable) { + canvas.requestFocus(); + + super.internalCreate(); + logger.info("Display created."); + + renderer.initialize(); + listener.initialize(); + } + + public void display(GLAutoDrawable glad) { + if (!created.get() && renderer != null){ + listener.destroy(); + logger.info("Canvas destroyed."); + super.internalDestroy(); + return; + } + + if (width != canvas.getWidth() || height != canvas.getHeight()){ + width = canvas.getWidth(); + height = canvas.getHeight(); + if (listener != null) + listener.reshape(width, height); + } + + boolean flush = autoFlush.get(); + if (flush && !wasAnimating){ + animator.start(); + wasAnimating = true; + }else if (!flush && wasAnimating){ + animator.stop(); + wasAnimating = false; + } + + listener.update(); + renderer.onFrame(); + + } + + public Canvas getCanvas() { + return canvas; + } + + @Override + public void dispose(GLAutoDrawable arg0) { + } + +} diff --git a/engine/src/jogl2/com/jme3/system/jogl/JoglContext.java b/engine/src/jogl2/com/jme3/system/jogl/JoglContext.java new file mode 100644 index 000000000..6f36f3092 --- /dev/null +++ b/engine/src/jogl2/com/jme3/system/jogl/JoglContext.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.jogl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.jogl.JoglRenderer; +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.JmeContext; +import com.jme3.system.NanoTimer; +import com.jme3.system.Timer; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class JoglContext implements JmeContext { + + protected AtomicBoolean created = new AtomicBoolean(false); + protected final Object createdLock = new Object(); + + protected AppSettings settings = new AppSettings(true); + protected JoglRenderer renderer; + protected Timer timer; + protected SystemListener listener; + + protected AwtKeyInput keyInput; + protected AwtMouseInput mouseInput; + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + } + + public AppSettings getSettings() { + return settings; + } + + public Renderer getRenderer() { + return renderer; + } + + public MouseInput getMouseInput() { + return mouseInput; + } + + public KeyInput getKeyInput() { + return keyInput; + } + + public JoyInput getJoyInput() { + return null; + } + + public Timer getTimer() { + return timer; + } + + public boolean isCreated() { + return created.get(); + } + + 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 void internalCreate() { + timer = new NanoTimer(); + synchronized (createdLock){ + created.set(true); + createdLock.notifyAll(); + } + // renderer initialization must happen in subclass. + } + + protected void internalDestroy() { + renderer = null; + timer = null; + + synchronized (createdLock){ + created.set(false); + createdLock.notifyAll(); + } + } + +} diff --git a/engine/src/jogl2/com/jme3/system/jogl/JoglDisplay.java b/engine/src/jogl2/com/jme3/system/jogl/JoglDisplay.java new file mode 100644 index 000000000..042f3faa6 --- /dev/null +++ b/engine/src/jogl2/com/jme3/system/jogl/JoglDisplay.java @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.jogl; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.DisplayMode; +import java.awt.Frame; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.media.opengl.GLAutoDrawable; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +import com.jme3.system.AppSettings; + +public class JoglDisplay extends JoglAbstractDisplay { + + private static final Logger logger = Logger.getLogger(JoglDisplay.class.getName()); + + protected AtomicBoolean windowCloseRequest = new AtomicBoolean(false); + + protected AtomicBoolean needClose = new AtomicBoolean(false); + + protected AtomicBoolean needRestart = new AtomicBoolean(false); + + protected boolean wasInited = false; + + protected Frame frame; + + public Type getType() { + return Type.Display; + } + + private int getClosestValidBitDepth(DisplayMode[] modes, final int bpp) { + final int validBitsPerPixels; + // check the bit depth (bits per pixel) + boolean isBitDepthValid = false; + for (DisplayMode mode : modes) { + if (mode != null && mode.getBitDepth() == bpp) { + isBitDepthValid = true; + break; + } + } + if (!isBitDepthValid) { + // use the closest valid bit depth + int bestSupportedBitDepth = Integer.MAX_VALUE; + int biggestBitDepth = -Integer.MIN_VALUE; + for (DisplayMode mode : modes) { + if (mode != null) { + if (Math.abs(mode.getBitDepth() - bpp) < Math.abs(bestSupportedBitDepth - bpp)) { + bestSupportedBitDepth = mode.getBitDepth(); + } + if (mode.getBitDepth() > biggestBitDepth) { + biggestBitDepth = mode.getBitDepth(); + } + } + + } + if (bestSupportedBitDepth == Integer.MAX_VALUE) { + validBitsPerPixels = biggestBitDepth; + } + else { + validBitsPerPixels = bestSupportedBitDepth; + } + } + else { + validBitsPerPixels = bpp; + } + return validBitsPerPixels; + } + + private Dimension getClosestValidDisplaySize(DisplayMode[] modes, final int width, + final int height) { + Dimension validDisplaySize = new Dimension(width, height); + if (width <= 0 || height <= 0) { + // leave the size unchanged + validDisplaySize.width = device.getDisplayMode().getWidth(); + validDisplaySize.height = device.getDisplayMode().getHeight(); + } + else { + boolean isDimensionValid = false; + for (DisplayMode mode : modes) { + if (mode.getWidth() == width && mode.getHeight() == height) { + isDimensionValid = true; + break; + } + } + if (!isDimensionValid) { + int bestSupportedWidth = Integer.MAX_VALUE; + int bestSupportedHeight = Integer.MAX_VALUE; + int biggestSupportedWidth = Integer.MIN_VALUE; + int biggestSupportedHeight = Integer.MIN_VALUE; + // use the closest supported width + for (DisplayMode mode : modes) { + if (Math.abs(mode.getWidth() - width) < Math.abs(bestSupportedWidth - width)) { + bestSupportedWidth = mode.getWidth(); + bestSupportedHeight = mode.getHeight(); + } + if (mode.getWidth() > biggestSupportedWidth) { + biggestSupportedWidth = mode.getWidth(); + biggestSupportedHeight = mode.getHeight(); + } + } + // if the width was really too big, use the screen width + if (bestSupportedWidth == Integer.MAX_VALUE) { + validDisplaySize.width = biggestSupportedWidth; + validDisplaySize.height = biggestSupportedHeight; + } + else { + validDisplaySize.width = bestSupportedWidth; + validDisplaySize.height = bestSupportedHeight; + } + } + } + // keep only valid modes + for (int modeIndex = 0; modeIndex < modes.length; modeIndex++) { + if (modes[modeIndex] != null && modes[modeIndex].getWidth() != validDisplaySize.width + && modes[modeIndex].getHeight() != validDisplaySize.height) { + modes[modeIndex] = null; + } + } + return validDisplaySize; + } + + private int getClosestValidRefreshRate(DisplayMode[] modes, final int freq) { + final int validRefreshRate; + boolean isRefreshRateValid = false; + for (DisplayMode mode : modes) { + if (mode != null && mode.getRefreshRate() == freq) { + isRefreshRateValid = true; + break; + } + } + if (!isRefreshRateValid) { + int bestSupportedRefreshRate = Integer.MAX_VALUE; + int biggestRefreshRate = -Integer.MIN_VALUE; + for (DisplayMode mode : modes) { + if (mode != null) { + if (Math.abs(mode.getRefreshRate() - freq) < Math.abs(bestSupportedRefreshRate + - freq)) { + bestSupportedRefreshRate = mode.getRefreshRate(); + } + if (mode.getRefreshRate() > biggestRefreshRate) { + biggestRefreshRate = mode.getRefreshRate(); + } + } + } + if (bestSupportedRefreshRate == Integer.MAX_VALUE) { + validRefreshRate = biggestRefreshRate; + } + else { + validRefreshRate = bestSupportedRefreshRate; + } + } + else { + validRefreshRate = freq; + } + return validRefreshRate; + } + + protected DisplayMode getFullscreenDisplayMode(final DisplayMode[] modes, int width, + int height, int bpp, int freq) { + DisplayMode[] validModes = new DisplayMode[modes.length]; + System.arraycopy(modes, 0, validModes, 0, modes.length); + Dimension validDisplaySize = getClosestValidDisplaySize(validModes, width, height); + bpp = getClosestValidBitDepth(validModes, bpp); + width = validDisplaySize.width; + height = validDisplaySize.height; + freq = getClosestValidRefreshRate(validModes, freq); + // Now, the bit depth, the refresh rate, the width and the height are valid + for (DisplayMode mode : validModes) { + if (mode != null + && (mode.getWidth() == width && mode.getHeight() == height + && mode.getBitDepth() == bpp || (mode.getBitDepth() == 32 && bpp == 24) + && mode.getRefreshRate() == freq)) { + return mode; + } + } + return null; + } + + protected void createGLFrame() { + Container contentPane; + if (useAwt) { + frame = new Frame(settings.getTitle()); + contentPane = frame; + } + else { + frame = new JFrame(settings.getTitle()); + contentPane = ((JFrame) frame).getContentPane(); + } + + contentPane.setLayout(new BorderLayout()); + + applySettings(settings); + + frame.setResizable(false); + frame.setFocusable(true); + + if (settings.getIcons() != null) { + try { + Method setIconImages = frame.getClass().getMethod("setIconImages", List.class); + setIconImages.invoke(frame, Arrays.asList(settings.getIcons())); + } catch (Exception e) { + e.printStackTrace(); + } + } + + canvas.setSize(settings.getWidth(), settings.getHeight()); + // only add canvas after frame is visible + contentPane.add(canvas, BorderLayout.CENTER); + if (!useAwt) { + contentPane.setSize(settings.getWidth(), settings.getHeight()); + } + frame.setSize(settings.getWidth(), settings.getHeight()); + frame.setPreferredSize(new Dimension(settings.getWidth(), settings.getHeight())); + // N.B: do not use pack() + + if (device.getFullScreenWindow() == null) { + // now that canvas is attached, + // determine optimal size to contain it + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation((screenSize.width - frame.getWidth()) / 2, + (screenSize.height - frame.getHeight()) / 2); + } + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent evt) { + windowCloseRequest.set(true); + } + + @Override + public void windowActivated(WindowEvent evt) { + active.set(true); + } + + @Override + public void windowDeactivated(WindowEvent evt) { + active.set(false); + } + }); + } + + protected void applySettings(AppSettings settings) { + DisplayMode displayMode; + if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { + displayMode = device.getDisplayMode(); + settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); + } + else if (settings.isFullscreen()) { + displayMode = getFullscreenDisplayMode(device.getDisplayModes(), settings.getWidth(), + settings.getHeight(), settings.getBitsPerPixel(), settings.getFrequency()); + if (displayMode == null) { + throw new RuntimeException( + "Unable to find fullscreen display mode matching settings"); + } + } + else { + displayMode = getFullscreenDisplayMode(device.getDisplayModes(), settings.getWidth(), + settings.getHeight(), settings.getBitsPerPixel(), settings.getFrequency()); + } + settings.setWidth(displayMode.getWidth()); + settings.setHeight(displayMode.getHeight()); + settings.setBitsPerPixel(displayMode.getBitDepth()); + settings.setFrequency(displayMode.getRefreshRate()); + + // FIXME: seems to return false even though + // it is supported.. + // if (!device.isDisplayChangeSupported()){ + // // must use current device mode if display mode change not supported + // displayMode = device.getDisplayMode(); + // settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); + // } + + frameRate = settings.getFrameRate(); + logger.log( + Level.INFO, + "Selected display mode: {0}x{1}x{2} @{3}", + new Object[] { settings.getWidth(), settings.getHeight(), + settings.getBitsPerPixel(), settings.getFrequency() }); + + canvas.setSize(settings.getWidth(), settings.getHeight()); + + DisplayMode prevDisplayMode = device.getDisplayMode(); + + frame.setUndecorated(settings.isFullscreen()); + if (settings.isFullscreen()) { + if (device.isFullScreenSupported()) { + try { + device.setFullScreenWindow(frame); + if (!prevDisplayMode.equals(displayMode) && device.isDisplayChangeSupported()) { + device.setDisplayMode(displayMode); + } + } + catch (Throwable t) { + logger.log(Level.SEVERE, "Failed to enter fullscreen mode", t); + device.setFullScreenWindow(null); + } + } + else { + logger.warning("Fullscreen not supported."); + } + } + else { + if (device.getFullScreenWindow() == frame) { + device.setFullScreenWindow(null); + } + } + frame.setVisible(true); + } + + private void initInEDT() { + initGLCanvas(); + + createGLFrame(); + + startGLCanvas(); + } + + public void init(GLAutoDrawable drawable) { + // prevent initializing twice on restart + if (!wasInited) { + canvas.requestFocus(); + + super.internalCreate(); + logger.info("Display created."); + + renderer.initialize(); + listener.initialize(); + + wasInited = true; + } + } + + public void create(boolean waitFor) { + try { + if (waitFor) { + try { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + initInEDT(); + } + }); + } + catch (InterruptedException ex) { + listener.handleError("Interrupted", ex); + } + } + else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + initInEDT(); + } + }); + } + } + catch (InvocationTargetException ex) { + throw new AssertionError(); // can never happen + } + } + + public void destroy(boolean waitFor) { + needClose.set(true); + if (waitFor) { + waitFor(false); + } + } + + public void restart() { + if (created.get()) { + needRestart.set(true); + } + else { + throw new IllegalStateException("Display not started yet. Cannot restart"); + } + } + + public void setTitle(String title) { + if (frame != null) { + frame.setTitle(title); + } + } + + /** + * Callback. + */ + public void display(GLAutoDrawable drawable) { + if (needClose.get()) { + listener.destroy(); + animator.stop(); + if (settings.isFullscreen()) { + device.setFullScreenWindow(null); + } + frame.dispose(); + logger.info("Display destroyed."); + super.internalDestroy(); + return; + } + + if (windowCloseRequest.get()) { + listener.requestClose(false); + windowCloseRequest.set(false); + } + + if (needRestart.getAndSet(false)) { + // for restarting contexts + if (frame.isVisible()) { + animator.stop(); + frame.dispose(); + createGLFrame(); + startGLCanvas(); + } + } + + // boolean flush = autoFlush.get(); + // if (animator.isAnimating() != flush){ + // if (flush) + // animator.stop(); + // else + // animator.start(); + // } + + if (wasActive != active.get()) { + if (!wasActive) { + listener.gainFocus(); + wasActive = true; + } + else { + listener.loseFocus(); + wasActive = false; + } + } + + listener.update(); + renderer.onFrame(); + } +} diff --git a/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java b/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java new file mode 100644 index 000000000..303f0f664 --- /dev/null +++ b/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java @@ -0,0 +1,988 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.ListenerParam; +import com.jme3.audio.AudioParam; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioNode.Status; +import com.jme3.audio.AudioStream; +import com.jme3.audio.Environment; +import com.jme3.audio.Filter; +import com.jme3.audio.Listener; +import com.jme3.audio.LowPassFilter; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +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 org.lwjgl.openal.AL; + +import org.lwjgl.openal.AL11; +import org.lwjgl.openal.ALC10; +import org.lwjgl.openal.ALCdevice; +import org.lwjgl.openal.EFX10; +import org.lwjgl.openal.OpenALException; + +import static org.lwjgl.openal.AL10.*; + +public class LwjglAudioRenderer implements AudioRenderer, Runnable { + + private static final Logger logger = Logger.getLogger(LwjglAudioRenderer.class.getName()); + + private static final int BUFFER_SIZE = 8192; + 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 = ByteBuffer.allocateDirect(BUFFER_SIZE); + private final byte[] arrayBuf = new byte[BUFFER_SIZE]; + + private int[] channels; + private AudioNode[] chanSrcs; + private int nextChan = 0; + private ArrayList freeChans = new ArrayList(); + + private Listener listener; + private boolean audioDisabled = false; + + private boolean supportEfx = false; + private int auxSends = 0; + private int reverbFx = -1; + private int reverbFxSlot = -1; + + private static final float UPDATE_RATE = 0.01f; + private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); + private final AtomicBoolean threadLock = new AtomicBoolean(false); + + public LwjglAudioRenderer(){ + nativeBuf.order(ByteOrder.nativeOrder()); + } + + 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{ + 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; + } + + 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 channelList = new ArrayList(); + 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); + + ALCdevice device = AL.getDevice(); + supportEfx = ALC10.alcIsExtensionPresent(device, "ALC_EXT_EFX"); + logger.log(Level.FINER, "Audio EFX support: {0}", supportEfx); + + 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); + } + } + + public void cleanupInThread(){ + if (audioDisabled){ + AL.destroy(); + return; + } + + // delete channel-based sources + ib.clear(); + ib.put(channels); + ib.flip(); + alDeleteSources(ib); + + if (supportEfx){ + ib.position(0).limit(1); + ib.put(0, reverbFx); + EFX10.alDeleteEffects(ib); + + ib.position(0).limit(1); + ib.put(0, reverbFxSlot); + EFX10.alDeleteAuxiliaryEffectSlots(ib); + } + + // XXX: Delete other buffers/sources + 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); + } + + 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; + + 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 (!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 (!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{ + 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 (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()); + + if (src.isReverbEnabled()){ + 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){ + 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) + 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){ + 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){ + // detach filter + alSourcei(sourceId, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); + } + if (src.isPositional()){ + AudioNode pas = (AudioNode) src; + if (pas.getReverbFilter() != null){ + 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(null, -1); + clearChannel(i); + freeChannel(i); + } + } + }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(null, -1); + } + clearChannel(i); + freeChannel(i); + } + } + } + } + + 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; + 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 " + src); + return; + } + clearChannel(index); + src.setChannel(this, 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(null, -1); + clearChannel(chan); + freeChannel(chan); + } + } + } + + 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); + } + + 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); + + 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 deleteAudioData(AudioData ad){ + 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-ogl/com/jme3/input/lwjgl/JInputJoyInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/JInputJoyInput.java new file mode 100644 index 000000000..b754e15ed --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/JInputJoyInput.java @@ -0,0 +1,201 @@ +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; +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.Controller; +import net.java.games.input.ControllerEnvironment; +import net.java.games.input.Event; +import net.java.games.input.EventQueue; +import net.java.games.input.Rumbler; + +public class JInputJoyInput implements JoyInput { + + private boolean inited = false; + private Joystick[] joysticks; + private RawInputListener listener; + + private HashMap[] buttonIdsToIndices; + private HashMap[] axisIdsToIndices; + private HashMap controllerToIndices; + private IntMap 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(); + indicesToController = new IntMap(); + Controller[] cs = ce.getControllers(); + 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; + + 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.UNKNOWN + || c.getType() == Controller.Type.KEYBOARD + || c.getType() == Controller.Type.MOUSE) + continue; + + buttonIdsToIndices[joyIndex] = new HashMap(); + axisIdsToIndices[joyIndex] = new HashMap(); + 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-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java new file mode 100644 index 000000000..7b22c5a11 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.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.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.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.Controller; +import org.lwjgl.input.Controllers; + +@Deprecated +class LwjglJoyInput implements JoyInput { + + private static final Logger logger = Logger.getLogger(LwjglKeyInput.class.getName()); + + private RawInputListener listener; + private boolean enabled = false; + + public void initialize() { + try { + Controllers.create(); + if (Controllers.getControllerCount() == 0 || !Controllers.isCreated()){ + logger.warning("Joysticks disabled."); + return; + } + logger.info("Joysticks created."); + enabled = true; + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Failed to create joysticks", ex); + } + } + + public int getJoyCount() { + return Controllers.getControllerCount(); + } + + public String getJoyName(int joyIndex) { + return Controllers.getController(joyIndex).getName(); + } + + public int getAxesCount(int joyIndex) { + return Controllers.getController(joyIndex).getAxisCount(); + } + + public int getButtonCount(int joyIndex) { + return Controllers.getController(joyIndex).getButtonCount(); + } + + private void printController(Controller c){ + System.out.println("Name: "+c.getName()); + System.out.println("Index: "+c.getIndex()); + System.out.println("Button Count: "+c.getButtonCount()); + System.out.println("Axis Count: "+c.getAxisCount()); + + int buttons = c.getButtonCount(); + for (int b = 0; b < buttons; b++) { + System.out.println("Button " + b + " = " + c.getButtonName(b)); + } + + int axis = c.getAxisCount(); + for (int b = 0; b < axis; b++) { + System.out.println("Axis " + b + " = " + c.getAxisName(b)); + } + } + + public void update() { + if (!enabled) + return; + + Controllers.poll(); + while (Controllers.next()){ + Controller c = Controllers.getEventSource(); + if (Controllers.isEventAxis()){ + int realAxis = Controllers.getEventControlIndex(); + JoyAxisEvent evt = new JoyAxisEvent(c.getIndex(), + realAxis, + c.getAxisValue(realAxis)); + listener.onJoyAxisEvent(evt); + }else if (Controllers.isEventPovX()){ + JoyAxisEvent evt = new JoyAxisEvent(c.getIndex(), + JoyInput.AXIS_POV_X, + c.getPovX()); + listener.onJoyAxisEvent(evt); + }else if (Controllers.isEventPovY()){ + JoyAxisEvent evt = new JoyAxisEvent(c.getIndex(), + JoyInput.AXIS_POV_Y, + c.getPovY()); + listener.onJoyAxisEvent(evt); + }else if (Controllers.isEventButton()){ + int btn = Controllers.getEventControlIndex(); + JoyButtonEvent evt = new JoyButtonEvent(c.getIndex(), + btn, + c.isButtonPressed(btn)); + listener.onJoyButtonEvent(evt); + } + } + Controllers.clearEvents(); + } + + public void destroy() { + if (!enabled) + return; + + Controllers.destroy(); + logger.info("Joysticks destroyed."); + } + + public boolean isInitialized() { + if (!enabled) + return false; + + return Controllers.isCreated(); + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS; + } + + public void setJoyRumble(int joyId, float amount){ + } + + public Joystick[] loadJoysticks(InputManager inputManager) { + int count = Controllers.getControllerCount(); + Joystick[] joysticks = new Joystick[count]; + for (int i = 0; i < count; i++){ + Controller c = Controllers.getController(i); + Joystick j = new Joystick(inputManager, + this, + i, + c.getName(), + c.getButtonCount(), + c.getAxisCount(), + -1,-1); + joysticks[i] = j; + } + return joysticks; + } + +} diff --git a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.java new file mode 100644 index 000000000..9234013b2 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.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.input.lwjgl; + +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.RawInputListener; +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 RawInputListener listener; + + public void initialize() { + try { + Keyboard.create(); + Keyboard.enableRepeatEvents(true); + logger.info("Keyboard created."); + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Error while creating keyboard.", ex); + } + } + + public boolean isKeyDown(int key){ + return Keyboard.isKeyDown(key); + } + + public int getKeyCount(){ + return Keyboard.KEYBOARD_SIZE; + } + + public void update() { + 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() { + 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-ogl/com/jme3/input/lwjgl/LwjglMouseInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglMouseInput.java new file mode 100644 index 000000000..58ba58a6b --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglMouseInput.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.input.lwjgl; + +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListener; +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 RawInputListener listener; + + private boolean supportHardwareCursor = false; + private boolean cursorVisible = true; + + private int curX, curY, curWheel; + + public void initialize() { + try { + Mouse.create(); + logger.info("Mouse created."); + supportHardwareCursor = (Cursor.getCapabilities() & Cursor.CURSOR_ONE_BIT_TRANSPARENCY) != 0; + } 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() { + 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); + listener.onMouseMotionEvent(evt); + } + if (btn != -1){ + MouseButtonEvent evt = new MouseButtonEvent(btn, + Mouse.getEventButtonState(), x, y); + listener.onMouseButtonEvent(evt); + } + } + } + + public void destroy() { + Mouse.destroy(); + logger.info("Mouse destroyed."); + } + + public void setCursorVisible(boolean visible){ + Mouse.setGrabbed(!visible); + cursorVisible = 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-ogl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java new file mode 100644 index 000000000..f13489b39 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java @@ -0,0 +1,878 @@ +package com.jme3.renderer.lwjgl; + +import com.jme3.renderer.GL1Renderer; +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.texture.FrameBuffer; +import java.nio.ShortBuffer; +import java.nio.Buffer; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.IntMap.Entry; +import com.jme3.util.IntMap; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.texture.Texture; +import com.jme3.light.LightList; +import com.jme3.material.FixedFuncBinding; +import java.nio.FloatBuffer; +import com.jme3.math.Matrix4f; +import java.util.logging.Level; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GLObjectManager; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.Statistics; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.EnumSet; +import java.util.logging.Logger; +import jme3tools.converters.MipMapGenerator; + +import static org.lwjgl.opengl.GL11.*; + +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 RenderContext context = new RenderContext(); + private final GLObjectManager objManager = new GLObjectManager(); + + private int maxTexSize; + private int maxCubeTexSize; + private int maxVertCount; + private int maxTriCount; + + 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 Matrix4f projMatrix = new Matrix4f(); + + private boolean colorSet = false; + + 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 getCaps() { + return EnumSet.noneOf(Caps.class); + } + + public void initialize() { + //glDisable(GL_DEPTH_TEST); + glShadeModel(GL_SMOOTH); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + } + + public void resetGLObjects() { + colorSet = false; + + objManager.resetObjects(); + statistics.clearMemory(); + context.reset(); + } + + public void cleanup() { + objManager.deleteAllObjects(this); + statistics.clearMemory(); + } + + public void setDepthRange(float start, float end) { + glDepthRange(start, end); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + bits = GL_COLOR_BUFFER_BIT; + } + if (depth) { + 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 setFixedFuncBinding(FixedFuncBinding ffBinding, Object val){ + switch (ffBinding){ + case Color: + ColorRGBA color = (ColorRGBA) val; + glColor4f(color.r, color.g, color.b, color.a); + colorSet = true; + break; + } + } + + public void clearSetFixedFuncBindings(){ + if (colorSet){ + glColor4f(1,1,1,1); + colorSet = false; + } + } + + 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(); + } + + } + + 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; + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + if (context.matrixMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + context.matrixMode = GL_MODELVIEW; + } + + glLoadMatrix(storeMatrix(viewMatrix, fb16)); + glMultMatrix(storeMatrix(worldMatrix, fb16)); + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + if (context.matrixMode != GL_PROJECTION) { + glMatrixMode(GL_PROJECTION); + context.matrixMode = GL_PROJECTION; + } + + storeMatrix(projMatrix, fb16); + glLoadMatrix(fb16); + + this.viewMatrix.set(viewMatrix); + } + + public void setLighting(LightList list) { + if (list == null || list.size() == 0) { + // turn off lighting + //glDisable(GL_LIGHTING); + return; + } + +// glEnable(GL_LIGHTING); + + //TODO: ... + } + + 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: + 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); + } + + if (!img.hasMipmaps() && mips) { + // No pregenerated mips available, + // generate from base level if required +// glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE); + // TODO: Generate mipmaps here + MipMapGenerator.generateMipMaps(img); + } else { + } + + /* + if (target == GL_TEXTURE_CUBE_MAP) { + List 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 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 checkTexturingUsed() { + 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; + } + } + + 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 buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry 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()); + } + clearVertexAttribs(); + checkTexturingUsed(); + clearSetFixedFuncBindings(); + } + + 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(); + + if (mesh.getNumLodLevels() == 0) { + IntMap bufs = mesh.getBuffers(); + for (Entry 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 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-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java new file mode 100644 index 000000000..6af931a61 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -0,0 +1,2194 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GLObjectManager; +import com.jme3.renderer.IDList; +import com.jme3.renderer.Renderer; +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.renderer.RenderContext; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.Statistics; +import com.jme3.scene.Mesh.Mode; +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 java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +//import org.lwjgl.opengl.ARBGeometryShader4; +//import org.lwjgl.opengl.ARBHalfFloatVertex; +//import org.lwjgl.opengl.ARBVertexArrayObject; +//import org.lwjgl.opengl.ARBHalfFloatVertex; +//import org.lwjgl.opengl.ARBVertexArrayObject; +import org.lwjgl.opengl.ARBDrawBuffers; +//import org.lwjgl.opengl.ARBDrawInstanced; +import org.lwjgl.opengl.ARBDrawInstanced; +import org.lwjgl.opengl.ARBMultisample; +import org.lwjgl.opengl.ContextCapabilities; +import org.lwjgl.opengl.EXTTextureArray; +import org.lwjgl.opengl.EXTTextureFilterAnisotropic; +import org.lwjgl.opengl.GLContext; +import org.lwjgl.opengl.NVHalfFloat; + +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 static org.lwjgl.opengl.EXTFramebufferObject.*; +import static org.lwjgl.opengl.EXTFramebufferMultisample.*; +import static org.lwjgl.opengl.ARBTextureMultisample.*; +import static org.lwjgl.opengl.EXTFramebufferBlit.*; +import org.lwjgl.opengl.ARBShaderObjects.*; +import org.lwjgl.opengl.ARBTextureMultisample; +import org.lwjgl.opengl.ARBVertexArrayObject; +import org.lwjgl.opengl.GL30; +//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 GLObjectManager objManager = new GLObjectManager(); + private final EnumSet 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 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 getCaps() { + return caps; + } + + 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); + + 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); + } + + 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 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) { + glDepthRange(start, end); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + bits = GL_COLOR_BUFFER_BIT; + } + if (depth) { + 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 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) { + glEnable(GL_POINT_SPRITE); + glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); + context.pointSprite = true; + } else if (!state.isPointSprite() && context.pointSprite) { + glDisable(GL_POINT_SPRITE); + 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(); + } + + } + + /*********************************************************************\ + |* 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.", 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) { + glUseProgram(shaderId); + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + + 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 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 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 RuntimeException("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); + } + + // 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: " ); + } + } + + 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 + 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)) { + 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: " ); + } + } + + 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) { + 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); + if (context.boundShaderProgram != shader.getId()) { + if (VALIDATE_SHADER) { + // check if shader can be used + // with current state + glValidateProgram(shader.getId()); + glGetProgram(shader.getId(), GL_VALIDATE_STATUS, intBuf1); + boolean validateOK = intBuf1.get(0) == GL_TRUE; + if (validateOK) { + logger.fine("shader validate success"); + } else { + logger.warning("shader validate failure"); + } + } + + 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(); + 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()); + // the next part is done by the GLObjectManager automatically +// glDeleteShader(source.getId()); + } + } + // 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) { + 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() { + 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) { + 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() > 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 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(), tex.getImage().getMultiSamples())); + } + } + } + + + 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) { + 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); + } + } + + 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()); + } + } + // 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)); + } + } + + if (target == GL_TEXTURE_CUBE_MAP) { + List 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 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) { + glGenerateMipmapEXT(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 RuntimeException("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 RuntimeException("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); + + 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; + } + } else { + target = GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + } + } + + 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 RuntimeException("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 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); + glDeleteBuffers(intBuf1); + vb.resetObject(); + } + } + + 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"); + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + int programId = context.boundShaderProgram; + if (programId > 0) { + Attribute attrib = boundShader.getAttribute(vb.getBufferType().name()); + 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); + } + } + + 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; + } + + 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; + } + + 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 buffers = mesh.getBuffers(); + for (Entry 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); + } + + if (context.boundVertexArray != mesh.getId()) { + ARBVertexArrayObject.glBindVertexArray(mesh.getId()); + context.boundVertexArray = mesh.getId(); + } + + IntMap buffers = mesh.getBuffers(); + VertexBuffer indices = null; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = buffers.get(Type.Index.ordinal()); + } + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { +// throw new UnsupportedOperationException("Cannot render without index buffer"); + glDrawArrays(convertElementMode(mesh.getMode()), 0, 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 buffers = mesh.getBuffers(); + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = buffers.get(Type.Index.ordinal()); + } + for (Entry 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 { +// throw new UnsupportedOperationException("Cannot render without index buffer"); + glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + 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(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + renderMeshDefault(mesh, lod, count); +// } + } + + public void setAlphaToCoverage(boolean value) { + if (value) { + glEnable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); + } else { + glDisable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); + } + } +} diff --git a/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/TextureUtil.java b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/TextureUtil.java new file mode 100644 index 000000000..b94aeeb94 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/TextureUtil.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 com.jme3.renderer.lwjgl; + +import org.lwjgl.opengl.EXTAbgr; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; +import org.lwjgl.opengl.ARBDepthBufferFloat; +import org.lwjgl.opengl.ARBHalfFloatPixel; +import org.lwjgl.opengl.ARBTextureFloat; +import org.lwjgl.opengl.ARBTextureMultisample; +import org.lwjgl.opengl.EXTPackedFloat; +import org.lwjgl.opengl.EXTTextureArray; +import org.lwjgl.opengl.EXTTextureSharedExponent; +import org.lwjgl.opengl.NVDepthBufferFloat; +import static org.lwjgl.opengl.EXTTextureCompressionS3TC.*; +import static org.lwjgl.opengl.EXTTextureCompressionLATC.*; +import static org.lwjgl.opengl.ATITextureCompression3DC.*; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL12.*; +import static org.lwjgl.opengl.GL13.*; +import static org.lwjgl.opengl.GL14.*; + +public class TextureUtil { + + 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(); + 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; + 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; + case Luminance16Alpha16: + internalFormat = GL_LUMINANCE16_ALPHA16; + format = GL_LUMINANCE_ALPHA; + dataType = GL_UNSIGNED_BYTE; + 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, + 0); + }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-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java new file mode 100644 index 000000000..c4f7af2b4 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.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.GL11; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GLContext; +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(LwjglDisplay.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; + + /** + * Does LWJGL display initialization in the OpenGL thread + */ + protected void initInThread(){ + try{ + if (!JmeSystem.isLowPermissions()){ + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + } + }); + } + + createContext(settings); +// String rendererStr = settings.getString("Renderer"); + + logger.info("Display created."); + 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 renderer = GL11.glGetString(GL11.GL_RENDERER); + logger.log(Level.INFO, "Renderer: {0}", renderer); + + if (GLContext.getCapabilities().OpenGL20){ + String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION); + logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang); + } + + created.set(true); + } catch (Exception 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. + + if (!created.get()){ + if (Display.isCreated()) + Display.destroy(); + + 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(); + assert checkGLError(); + + // calls swap buffers, etc. + try { + if (autoFlush){ + Display.update(); + }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 (autoFlush) + renderer.onFrame(); + } + + /** + * De-initialize in the OpenGL thread. + */ + protected void deinitInThread(){ + if (Display.isCreated()){ + renderer.cleanup(); + Display.destroy(); + }else{ + // If using canvas temporary closing, the display would + // be closed at this point + renderer.resetGLObjects(); + } + + 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 (Display.isCloseRequested()) + listener.requestClose(false); + + if (wasActive != Display.isActive()){ + if (!wasActive){ + listener.gainFocus(); + wasActive = true; + }else{ + listener.loseFocus(); + wasActive = false; + } + } + + runLoop(); + + if (needClose.get()) + break; + } + deinitInThread(); + } + + public JoyInput getJoyInput() { + return new JInputJoyInput(); + } + + public MouseInput getMouseInput() { + return new LwjglMouseInput(); + } + + public KeyInput getKeyInput() { + return new LwjglKeyInput(); + } + + 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-ogl/com/jme3/system/lwjgl/LwjglCanvas.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java new file mode 100644 index 000000000..1711ac346 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.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.system.lwjgl; + +import com.jme3.system.AppSettings; +import com.jme3.system.JmeCanvasContext; +import com.jme3.system.JmeContext.Type; +import java.awt.Canvas; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Controllers; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.PixelFormat; + +public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext { + + private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + private Canvas canvas; + private int width; + private int height; + + private AtomicBoolean reinitReq = new AtomicBoolean(false); + private final Object reinitReqLock = new Object(); + + private AtomicBoolean reinitAuth = new AtomicBoolean(false); + private final Object reinitAuthLock = new Object(); + + private Thread renderThread; + private boolean mouseWasGrabbed = false; +// private Pbuffer dummyCtx; + + public LwjglCanvas(){ + super(); + + canvas = new Canvas(){ + + @Override + public void addNotify(){ + super.addNotify(); + if (renderThread == null || renderThread.getState() == Thread.State.TERMINATED){ + if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED){ + logger.log(Level.INFO, "EDT: Creating OGL thread. Was terminated."); + }else{ + logger.log(Level.INFO, "EDT: Creating OGL thread."); + } + renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread.start(); + }else{ + if (needClose.get()) + return; + + logger.log(Level.INFO, "EDT: Sending re-init authorization.."); + + // reinitializing canvas + synchronized (reinitAuthLock){ + reinitAuth.set(true); + reinitAuthLock.notifyAll(); + } + } + } + + @Override + public void removeNotify(){ + if (needClose.get()){ + logger.log(Level.INFO, "EDT: Close requested. Not re-initing."); + return; + } + + // request to put context into reinit mode + // this waits until reinit is authorized + logger.log(Level.INFO, "EDT: Sending re-init request.."); + synchronized (reinitReqLock){ + reinitReq.set(true); + while (reinitReq.get()){ + try { + reinitReqLock.wait(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "EDT: Interrupted! ", ex); + } + } + // NOTE: reinitReq is now false. + } + logger.log(Level.INFO, "EDT: Acknowledged receipt of re-init request!"); + + super.removeNotify(); + } + }; + + canvas.setFocusable(true); + canvas.setIgnoreRepaint(true); + } + + @Override + public Type getType() { + return Type.Canvas; + } + + public void create(boolean waitFor){ + // 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() { + } + + public Canvas getCanvas(){ + return canvas; + } + + @Override + protected void runLoop(){ + boolean reinitNeeded; + synchronized (reinitReqLock){ + reinitNeeded = reinitReq.get(); + } + + if (reinitNeeded){ + logger.log(Level.INFO, "OGL: Re-init request received!"); + listener.loseFocus(); + + boolean mouseActive = Mouse.isCreated(); + boolean keyboardActive = Keyboard.isCreated(); + boolean joyActive = Controllers.isCreated(); + + if (mouseActive) + Mouse.destroy(); + if (keyboardActive) + Keyboard.destroy(); + if (joyActive) + Controllers.destroy(); + + pauseCanvas(); + + synchronized (reinitReqLock){ + reinitReq.set(false); + reinitReqLock.notifyAll(); + } + + // we got the reinit request, now we wait for reinit to happen.. + logger.log(Level.INFO, "OGL: Waiting for re-init authorization.."); + synchronized (reinitAuthLock){ + while (!reinitAuth.get()){ + try { + reinitAuthLock.wait(); + if (Thread.interrupted()) + throw new InterruptedException(); + } catch (InterruptedException ex) { + if (needClose.get()){ + logger.log(Level.INFO, "OGL: Re-init aborted. Closing display.."); + return; + } + + logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); + } + } + // NOTE: reinitAuth becamse true, now set it to false. + reinitAuth.set(false); + } + + logger.log(Level.INFO, "OGL: Re-init authorization received. Re-initializing.."); + restoreCanvas(); + + try { + if (mouseActive){ + Mouse.create(); + } + if (keyboardActive){ + Keyboard.create(); + } + if (joyActive){ + Controllers.create(); + } + } catch (LWJGLException ex){ + listener.handleError("Failed to re-init input", ex); + } + } + if (width != canvas.getWidth() || height != canvas.getHeight()){ + width = canvas.getWidth(); + height = canvas.getHeight(); + if (listener != null) + listener.reshape(width, height); + } + super.runLoop(); + } + + @Override + public void destroy(boolean waitFor){ + needClose.set(true); + if (renderThread != null && renderThread.isAlive()){ + renderThread.interrupt(); + // make sure it really does get interrupted + synchronized(reinitAuthLock){ + reinitAuthLock.notifyAll(); + } + } + if (waitFor) + waitFor(false); + } + + private void pauseCanvas(){ + if (Mouse.isCreated() && Mouse.isGrabbed()){ + Mouse.setGrabbed(false); + mouseWasGrabbed = true; + } + + logger.log(Level.INFO, "OGL: Destroying display (temporarily)"); + Display.destroy(); + } + + /** + * Called if canvas was removed and then restored unexpectedly + */ + private void restoreCanvas(){ + logger.log(Level.INFO, "OGL: Waiting for canvas to become displayable.."); + while (!canvas.isDisplayable()){ + try { + Thread.sleep(1); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); + } + } + renderer.resetGLObjects(); + logger.log(Level.INFO, "OGL: Creating display.."); + createContext(settings); + + logger.log(Level.INFO, "OGL: Waiting for display to become active.."); + while (!Display.isCreated()){ + try { + Thread.sleep(1); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); + } + } + logger.log(Level.INFO, "OGL: Display is active!"); + + try { + if (mouseWasGrabbed){ + Mouse.create(); + Mouse.setGrabbed(true); + mouseWasGrabbed = false; + } + + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + canvas.requestFocus(); + } + }); + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "restoreCanvas()", ex); + } + + listener.gainFocus(); + } + + @Override + protected void createContext(AppSettings settings) { + frameRate = settings.getFrameRate(); + Display.setVSyncEnabled(settings.isVSync()); + + try{ + Display.setParent(canvas); + PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + settings.getSamples()); + Display.create(pf); + Display.makeCurrent(); + }catch (LWJGLException ex){ + listener.handleError("Failed to parent canvas to display", ex); + } + } + +} diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java new file mode 100644 index 000000000..b27f3f6b9 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.renderer.Renderer; +import com.jme3.renderer.lwjgl.LwjglGL1Renderer; +import com.jme3.renderer.lwjgl.LwjglRenderer; +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.JmeContext; +import com.jme3.system.Timer; +import java.util.concurrent.atomic.AtomicBoolean; +import org.lwjgl.opengl.GLContext; + +/** + * A LWJGL implementation of a graphics context. + */ +public abstract class LwjglContext implements JmeContext { + + protected AtomicBoolean created = new AtomicBoolean(false); + protected final Object createdLock = new Object(); + + protected AppSettings settings = new AppSettings(true); + protected Renderer renderer; + protected Timer timer; + protected SystemListener listener; + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + public void internalDestroy(){ + renderer = null; + timer = null; + synchronized (createdLock){ + created.set(false); + createdLock.notifyAll(); + } + } + + public void internalCreate(){ + timer = new LwjglTimer(); + if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) + || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ + renderer = new LwjglRenderer(); + ((LwjglRenderer)renderer).initialize(); + }else if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL1)){ + renderer = new LwjglGL1Renderer(); + ((LwjglGL1Renderer)renderer).initialize(); + }else{ + throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); + } + synchronized (createdLock){ + created.set(true); + createdLock.notifyAll(); + } + } + + 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); + } + + public AppSettings getSettings(){ + return settings; + } + + public Renderer getRenderer() { + return renderer; + } + + public Timer getTimer() { + return timer; + } + +} diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java new file mode 100644 index 000000000..a50747816 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.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.system.lwjgl; + +import com.jme3.system.JmeContext.Type; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.DisplayMode; +import com.jme3.system.AppSettings; + +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.opengl.ARBMultisample; +import org.lwjgl.opengl.ContextAttribs; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.PixelFormat; + +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) { + logger.log(Level.SEVERE, "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()); + } + + PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + settings.getSamples()); + + 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())){ + Display.destroy(); + renderer.resetGLObjects(); + pixelFormatChanged = true; + } + pixelFormat = pf; + + Display.setTitle(settings.getTitle()); + if (displayMode != null) + Display.setDisplayMode(displayMode); + + if (settings.getIcons() != null) + Display.setIcon(imagesToByteBuffers(settings.getIcons())); + + Display.setFullscreen(settings.isFullscreen()); + Display.setVSyncEnabled(settings.isVSync()); + + if (!created.get() || pixelFormatChanged){ + 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); + } + Display.create(pixelFormat, attr); + }else{ + Display.create(pixelFormat); + } + + if (pixelFormatChanged && pixelFormat.getSamples() > 1){ + GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + } + + private ByteBuffer[] imagesToByteBuffers(BufferedImage[] images) { + ByteBuffer[] out = new ByteBuffer[images.length]; + for (int i = 0; i < images.length; i++) { + out[i] = imageToByteBuffer(images[i]); + } + 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); + } + + + 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(){ + 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); + } + +} diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java new file mode 100644 index 000000000..5b833d332 --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.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.system.lwjgl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +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.GL11; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GLContext; +import org.lwjgl.opengl.OpenGLException; +import org.lwjgl.opengl.Pbuffer; +import org.lwjgl.opengl.PixelFormat; +import org.lwjgl.opengl.Util; + + +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; + } + + pixelFormat = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + settings.getSamples()); + width = settings.getWidth(); + height = settings.getHeight(); + try{ + //String rendererStr = settings.getString("Renderer"); +// if (rendererStr.startsWith("LWJGL-OpenGL3")){ +// ContextAttribs attribs; +// if (rendererStr.equals("LWJGL-OpenGL3.1")){ +// attribs = new ContextAttribs(3, 1); +// }else{ +// attribs = new ContextAttribs(3, 0); +// } +// attribs.withForwardCompatible(true); +// attribs.withDebug(false); +// Display.create(pf, attribs); +// }else{ + pbuffer = new Pbuffer(width, height, pixelFormat, null); +// } + + pbuffer.makeCurrent(); + + logger.info("Offscreen buffer 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); + } + }); + + 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 renderer = GL11.glGetString(GL11.GL_RENDERER); + logger.log(Level.INFO, "Renderer: {0}", renderer); + + if (GLContext.getCapabilities().OpenGL20){ + String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION); + logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang); + } + + created.set(true); + } 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); + }catch (LWJGLException ex){ + listener.handleError("Failed to restore pbuffer content", ex); + } + } + + try{ + pbuffer.makeCurrent(); + }catch (LWJGLException ex){ + listener.handleError( "Error occured while making pbuffer current", ex); + } + + listener.update(); + assert checkGLError(); + + renderer.onFrame(); + } + + protected void deinitInThread(){ + 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 void setTitle(String title) { + } + +} diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.java new file mode 100644 index 000000000..497f25c6e --- /dev/null +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.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.system.lwjgl; + +import com.jme3.math.FastMath; +import com.jme3.system.Timer; +import java.util.logging.Logger; +import org.lwjgl.Sys; + +/** + * Timer 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. Timer is a + * singleton object and must be created via the getTimer 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()); + + 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 Timer object. All values will be + * initialized to it's default values. + */ + public LwjglTimer() { + reset(); + + //print timer resolution info + logger.info("Timer resolution: " + LWJGL_TIMER_RES + " ticks per second"); + } + + 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; + } + + /** + * getFrameRate returns the current frame rate since the last + * call to update. + * + * @return the current frame rate. + */ + public float getFrameRate() { + return lastFPS; + } + + public float getTimePerFrame() { + return lastTPF; + } + + /** + * update 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; + } + + /** + * toString returns the string representation of this timer + * in the format:
+ *
+ * jme.utility.Timer@1db699b
+ * Time: {LONG}
+ * FPS: {LONG}
+ * + * @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/connection/Client.java b/engine/src/networking/com/jme3/network/connection/Client.java new file mode 100644 index 000000000..590e904e6 --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/Client.java @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.connection; + +import com.jme3.network.events.ConnectionListener; +import com.jme3.network.events.MessageListener; +import com.jme3.network.message.ClientRegistrationMessage; +import com.jme3.network.message.DisconnectMessage; +import com.jme3.network.message.DiscoverHostMessage; +import com.jme3.network.message.Message; +import com.jme3.network.queue.MessageQueue; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.service.ServiceManager; +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Client extends ServiceManager implements MessageListener, ConnectionListener { + protected Logger log = Logger.getLogger(Client.class.getName()); + + protected static int clientIDCounter = 0; + protected int clientID; + protected long playerID = -1; + protected String label; + + protected boolean isConnected; + protected TCPConnection tcp; + protected UDPConnection udp; + + protected ConnectionRunnable + thread; + + protected MessageQueue messageQueue; + + // Client (connector) related. + protected SocketChannel tcpChannel; + protected DatagramChannel udpChannel; + + protected SocketAddress udpTarget; + + protected boolean isConnector; + + + /** + * Constructs this client. + */ + public Client() { + this(false); + } + + /** + * Construct this client, either as a server connector, or + * a real client. Internal method. + * + * @param connector Whether this client is a connector or not. + */ + Client(boolean connector) { + super(ServiceManager.CLIENT); + clientID = ++clientIDCounter; + this.label = "Client#" + clientID; + + isConnector = connector; + if (connector) { + isConnected = true; + } else { + if (tcp == null) tcp = new TCPConnection(label); + if (udp == null) udp = new UDPConnection(label); + } + + messageQueue = new MessageQueue(); + } + + /** + * Constructor providing custom instances of the clients and its addresses. + * + * @param tcp The TCPConnection instance to manage. + * @param udp The UDPConnection instance to manage. + * @param tcpAddress The TCP address to connect to. + * @param udpAddress The UDP address to connect to. + * @throws java.io.IOException When a connect error has occurred. + */ + public Client(TCPConnection tcp, UDPConnection udp, SocketAddress tcpAddress, SocketAddress udpAddress) throws IOException { + this(); + + this.tcp = tcp; + tcp.connect(tcpAddress); + + this.udp = udp; + udp.connect(udpAddress); + isConnected = true; + + registerInternalListeners(); + } + + /** + * Constructor for providing a TCP client instance. UDP will be disabled. + * + * @param tcp The TCPConnection instance. + * @param tcpAddress The address to connect to. + * @throws IOException When a connection error occurs. + */ + public Client(TCPConnection tcp, SocketAddress tcpAddress) throws IOException { + this(); + + this.tcp = tcp; + tcp.connect(tcpAddress); + isConnected = true; + + registerInternalListeners(); + } + + /** + * Constructor for providing a UDP client instance. TCP will be disabled. + * + * @param udp The UDP client instance. + * @param updAddress The address to connect to. + * @throws IOException When a connection error occurs. + */ + public Client(UDPConnection udp, SocketAddress updAddress) throws IOException { + this(); + + this.udp = udp; + udp.connect(updAddress); + isConnected = true; + + registerInternalListeners(); + } + + /** + * Simple constructor for providing TCP port and UDP port. Will bind using on + * all interfaces, on given ports. + * + * @param ip The IP address where the server are located. + * @param tcpPort The TCP port to use. + * @param udpPort The UDP port to use. + * @throws IOException When a connection error occurs. + */ + public Client(String ip, int tcpPort, int udpPort) throws IOException { + this(); + + tcp = new TCPConnection(label); + tcp.connect(new InetSocketAddress(ip, tcpPort)); + + udp = new UDPConnection(label); + udp.connect(new InetSocketAddress(ip, udpPort)); + isConnected = true; + + registerInternalListeners(); + } + + /** + * Connect method for when the no arg constructor was used. + * + * @param ip The IP address to connect to. + * @param tcpPort The TCP port to use. To turn off, use -1. + * @param udpPort The UDP port to use. To turn off, use -1. + * @throws IllegalArgumentException When an illegal argument was given. + * @throws java.io.IOException When a connection error occurs. + */ + public void connect(String ip, int tcpPort, int udpPort) throws IllegalArgumentException, IOException { + if (tcpPort == -1 && udpPort == -1) throw new IllegalArgumentException("No point in connect when you want to turn both the connections off."); + + if (tcpPort != -1) { + tcp.connect(new InetSocketAddress(ip, tcpPort)); + } + if (udpPort != -1) { + udp.connect(new InetSocketAddress(ip, udpPort)); + } + registerInternalListeners(); + isConnected = true; + } + + private void registerInternalListeners() { + if (tcp != null) { + tcp.addConnectionListener(this); + tcp.socketChannel.keyFor(tcp.selector).attach(this); + } + addMessageListener(this, DisconnectMessage.class); + } + + /** + * Send a message. Whether it's over TCP or UDP is determined by the message flag. + * + * @param message The message to send. + * @throws IOException When a writing error occurs. + */ + public void send(Message message) throws IOException { + if (!isConnected) throw new IOException("Not connected yet. Use connect() first."); + + try { + if (message.isReliable()) { + messageQueue.add(message); + if (!isConnector) { + tcp.socketChannel.keyFor(tcp.selector).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + } else { + tcpChannel.keyFor(tcp.selector).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + } + } else { + udp.sendObject(message); + } + } catch (CancelledKeyException e) { + // Client was disconnected. + } + } + + /** + * Send a message over TCP. If this client is a connector, it'll simply send to this + * connector. If the client is a normal client, this'll be sent to the server. + * + * @param object The object to send. + * @throws IOException When a writing error occurs. + * @deprecated Use send with the reliable message flag set to true. + */ + @Deprecated + public void sendTCP(Object object) throws IOException { + if (tcp == null) throw new IOException("No TCP client/server."); + if (!isConnected) throw new IOException("Not connected yet. Use connect() first."); + if (isConnector) { + tcp.sendObject(this, object); + } else { + tcp.sendObject(object); + } + } + + /** + * Send a message over UDP. If this client is a connector, it'll simply send to this + * connector. If the client is a normal client, this'll be sent to the server. + * + * @param object The object to send. + * @throws IOException When a writing error occurs. + * @deprecated Use send. + */ + @Deprecated + public void sendUDP(Object object) throws IOException { + if (udp == null) throw new IOException("No UDP client/server."); + if (!isConnected) throw new IOException("Not connected yet. Use connect() first."); + if (isConnector) { + udp.sendObject(this, object); + } else { + udp.sendObject(object); + } + } + + /** + * Disconnect from the server. + * + * @param type See DisconnectMessage for the available types. + * @throws IOException When a disconnection error occurs. + */ + public void disconnect(String type) throws IOException { + if (isConnector) return; + // Send a disconnect message to the server. + DisconnectMessage msg = new DisconnectMessage(); + msg.setType(type); + tcp.sendObject(msg); + udp.sendObject(msg); + + // We can disconnect now. + thread.setKeepAlive(false); + + // GC it. + thread = null; + + log.log(Level.INFO, "[{0}][???] Disconnected.", label); + isConnected = false; + } + + /** + * Disconnect from the server. + * + * @param msg The custom DisconnectMessage to use. + * @throws IOException When a disconnection error occurs. + */ + public void disconnect(DisconnectMessage msg) throws IOException { + if (isConnector) return; + // Send a disconnect message to the server. + tcp.sendObject(msg); + udp.sendObject(msg); + + // We can disconnect now. + thread.setKeepAlive(false); + + // GC it. + thread = null; + + log.log(Level.INFO, "[{0}][???] Disconnected.", label); + isConnected = false; + } + + /** + * Disconnect from the server with the default disconnection type: + * USER_REQUESTED. + * + * @throws IOException When a disconnection error occurs. + */ + public void disconnect() throws IOException { + disconnect(DisconnectMessage.USER_REQUESTED); + } + + /** + * Kick this client from the server, with given kick reason. + * + * @param reason The reason this client was kicked. + * @throws IOException When a writing error occurs. + */ + public void kick(String reason) throws IOException { + if (!isConnector) return; + DisconnectMessage message = new DisconnectMessage(); + message.setType(DisconnectMessage.KICK); + message.setReason(reason); + message.setReliable(true); + send(message); + + tcp.addToDisconnectionQueue(this); + + log.log(Level.INFO, "[Server#?][???] {0} got kicked with reason: {1}.", new Object[]{this, reason}); + } + + /** + * Kick this client from the server, with given kick reason. + * + * @param message The custom disconnect message. + * @throws IOException When a writing error occurs. + */ + public void kick(DisconnectMessage message) throws IOException { + if (!isConnector) return; + message.setReliable(true); + send(message); + + tcp.addToDisconnectionQueue(this); + + log.log(Level.INFO, "[Server#?][???] {0} got kicked with reason: {1}.", new Object[]{this, message.getReason()}); + } + + private void disconnectInternal(DisconnectMessage message) throws IOException { + DisconnectMessage dcMessage = (DisconnectMessage)message; + String type = dcMessage.getType(); + String reason = dcMessage.getReason(); + + log.log(Level.INFO, "[{0}][???] We got disconnected from the server ({1}: {2}).", new Object[]{ + label, + type, + reason + }); + + // We can disconnect now. + thread.setKeepAlive(false); + + // GC it. + thread = null; + + isConnected = false; + } + + public List discoverHosts(int port, int timeout) throws IOException { + ArrayList addresses = new ArrayList(); + + DatagramSocket socket = new DatagramSocket(); + ByteBuffer buffer = ByteBuffer.allocate(4); + + Serializer.writeClass(buffer, DiscoverHostMessage.class); + + buffer.flip(); + byte[] data = new byte[buffer.limit()]; + buffer.get(data); + + for (NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces())) { + for (InetAddress address : Collections.list(iface.getInetAddresses())) { + if (address instanceof Inet6Address || address.isLoopbackAddress()) continue; + byte[] ip = address.getAddress(); + ip[3] = -1; + socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), port)); + ip[2] = -1; + socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), port)); + } + } + log.log(Level.FINE, "[{0}][UDP] Started discovery on port {1}.", new Object[]{label, port}); + + long targetTime = System.currentTimeMillis() + timeout; + + DatagramPacket packet = new DatagramPacket(new byte[0], 0); + socket.setSoTimeout(1000); + while (System.currentTimeMillis() < targetTime) { + try { + socket.receive(packet); + if (addresses.contains(packet.getAddress())) continue; + addresses.add(packet.getAddress()); + log.log(Level.FINE, "[{0}][UDP] Discovered server on {1}.", new Object[]{label, packet.getAddress()}); + } catch (SocketTimeoutException ste) { + // Nothing to be done here. + } + } + + return addresses; + } + + public void setLabel(String label) { + this.label = label; + } + + /////////////// + + // Server client related stuff. + + public void setSocketChannel(SocketChannel channel) { + tcpChannel = channel; + } + + public SocketChannel getSocketChannel() { + return tcpChannel; + } + + public void setDatagramChannel(DatagramChannel channel) { + udpChannel = channel; + } + + public DatagramChannel getDatagramChannel() { + return udpChannel; + } + + public void setDatagramReceiver(SocketAddress address) { + udpTarget = address; + } + + public SocketAddress getDatagramReceiver() { + return udpTarget; + } + + public void setTCPConnection(TCPConnection con) { + tcp = con; + } + + public void setUDPConnection(UDPConnection con) { + udp = con; + } + + public TCPConnection getTCPConnection() { + return tcp; + } + + public UDPConnection getUDPConnection() { + return udp; + } + + public MessageQueue getMessageQueue() { + return messageQueue; + } + + /////////////// + + /** + * Start this client. + */ + public void start() + { + new Thread(thread = new ConnectionRunnable(tcp, udp)).start(); + } + + /** + * Start this client with given sleep time. Higher sleep times may affect the system's response time + * negatively, whereas lower values may increase CPU load. Use only when you're certain. + * + * @param sleep The sleep time. + */ + public void start(int sleep) { + new Thread(thread = new ConnectionRunnable(tcp, udp, sleep)).start(); + } + + public int getClientID() { + return clientID; + } + + public long getPlayerID() { + return playerID; + } + + public void setPlayerID(long id) { + playerID = id; + } + + public String toString() { + return label; + } + + public void addConnectionListener(ConnectionListener listener) { + if (tcp != null) tcp.addConnectionListener(listener); + if (udp != null) udp.addConnectionListener(listener); + } + + public void removeConnectionListener(ConnectionListener listener) { + if (tcp != null) tcp.removeConnectionListener(listener); + if (udp != null) udp.removeConnectionListener(listener); + } + + public void addMessageListener(MessageListener listener) { + if (tcp != null) tcp.addMessageListener(listener); + if (udp != null) udp.addMessageListener(listener); + } + + public void addMessageListener(MessageListener listener, Class... classes) { + for (Class c : classes) { + if (tcp != null) tcp.addMessageListener(c, listener); + if (udp != null) udp.addMessageListener(c, listener); + } + } + + public void removeMessageListener(MessageListener listener) { + if (tcp != null) tcp.removeMessageListener(listener); + if (udp != null) udp.removeMessageListener(listener); + } + + public void removeMessageListener(MessageListener listener, Class... classes) { + for (Class c : classes) { + if (tcp != null) tcp.removeMessageListener(c, listener); + if (udp != null) udp.removeMessageListener(c, listener); + } + } + + /// + + /** + * Add a message listener for a specific class. + * + * @param messageClass The class to listen for. + * @param listener The listener. + * @deprecated Use addMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void addIndividualMessageListener(Class messageClass, MessageListener listener) { + if (tcp != null) tcp.addIndividualMessageListener(messageClass, listener); + if (udp != null) udp.addIndividualMessageListener(messageClass, listener); + } + + /** + * Add a message listener for specific classes. + * + * @param messageClass The classes to listen for. + * @param listener The listener. + * @deprecated Use addMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void addIndividualMessageListener(Class[] messageClass, MessageListener listener) { + for (Class c : messageClass) { + if (tcp != null) tcp.addIndividualMessageListener(c, listener); + if (udp != null) udp.addIndividualMessageListener(c, listener); + } + } + + /** + * Remove a message listener for a specific class. + * + * @param messageClass The class to what this listener is registered. + * @param listener The listener. + * @deprecated Use removeMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void removeIndividualMessageListener(Class messageClass, MessageListener listener) { + if (tcp != null) tcp.removeIndividualMessageListener(messageClass, listener); + if (udp != null) udp.removeIndividualMessageListener(messageClass, listener); + } + + /** + *Remove a message listener for specific classes. + * + * @param messageClass The classes to remove for. + * @param listener The listener. + * @deprecated Use addMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void removeIndividualMessageListener(Class[] messageClass, MessageListener listener) { + for (Class c : messageClass) { + if (tcp != null) tcp.removeIndividualMessageListener(c, listener); + if (udp != null) udp.removeIndividualMessageListener(c, listener); + } + } + + public void messageReceived(Message message) { + try { + disconnectInternal((DisconnectMessage)message); + } catch (IOException e) { + log.log(Level.WARNING, "[{0}][???] Could not disconnect.", label); + } + } + + public void messageSent(Message message) { + + } + + public void objectReceived(Object object) { + + } + + public void objectSent(Object object) { + + } + + public void clientConnected(Client client) { + // We are a client. This means that we succeeded in connecting to the server. + if (!isConnected) return; + long time = System.currentTimeMillis(); + playerID = time; + ClientRegistrationMessage message = new ClientRegistrationMessage(); + message.setId(time); + try { + message.setReliable(false); + send(message); + message.setReliable(true); + send(message); + } catch (Exception e) { + e.printStackTrace(); + log.log(Level.SEVERE, "[{0}][???] Could not sent client registration message. Disconnecting.", label); + try { + disconnect(DisconnectMessage.ERROR); + } catch (IOException ie) {} + } + } + + public void clientDisconnected(Client client) { + if (thread != null) { + // We can disconnect now. + thread.setKeepAlive(false); + + // GC it. + thread = null; + } + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof Client || obj instanceof Integer)) { + return false; + } else if (obj instanceof Client){ + return ((Client)obj).getClientID() == getClientID(); + } else if (obj instanceof Integer) { + return ((Integer)obj).intValue() == getClientID(); + } else { + return false; + } + } + +} diff --git a/engine/src/networking/com/jme3/network/connection/ClientManager.java b/engine/src/networking/com/jme3/network/connection/ClientManager.java new file mode 100644 index 000000000..1a1ee5d20 --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/ClientManager.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.network.connection; + +import com.jme3.network.events.ConnectionListener; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.ClientRegistrationMessage; +import com.jme3.network.message.Message; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.logging.Logger; + +/** + * The ClientManager is an internal class that deals with client registrations and disconnects. + * + * @author Lars Wesselius + */ +public class ClientManager extends MessageAdapter implements ConnectionListener { + protected Logger log = Logger.getLogger(ClientManager.class.getName()); + private ArrayList clients = new ArrayList(); + private Hashtable clientsByClientID = new Hashtable(); + + private ArrayList pendingMessages = new ArrayList(); + + private ArrayList connectionListeners = new ArrayList(); + + private ClientRegistrationMessage findMessage(long playerId) { + for (ClientRegistrationMessage message : pendingMessages) { + if (message.getId() == playerId) { + return message; + } + } + return null; + } + + public List getConnectors() { + return Collections.unmodifiableList(clients); + } + + public Client getClient(long playerId) { + for (Client client : clients) { + if (client.getPlayerID() == playerId) return client; + } + return null; + } + + public Client getClientByClientID(int clientID) { + return clientsByClientID.get(clientID); + } + + public boolean isClientConnected(Client client) { + return clients.contains(client); + } + + + @Override + public void messageReceived(Message message) { + ClientRegistrationMessage regMessage = (ClientRegistrationMessage)message; + ClientRegistrationMessage existingMessage = findMessage(regMessage.getId()); + + // Check if message exists, if not add this message to the pending queue. + if (existingMessage == null) { + pendingMessages.add(regMessage); + return; + } + + // We've got two messages of which we can construct a client. + Client client = new Client(true); + + Connection conOne = regMessage.getConnection(); + Connection conTwo = existingMessage.getConnection(); + + if (conOne instanceof TCPConnection) { + fillInTCPInfo(client, regMessage); + } else if (conOne instanceof UDPConnection) { + fillInUDPInfo(client, regMessage); + } + + if (conTwo instanceof TCPConnection) { + fillInTCPInfo(client, existingMessage); + } else if (conTwo instanceof UDPConnection) { + fillInUDPInfo(client, existingMessage); + } + + if (client.getUDPConnection() == null || client.getTCPConnection() == null) { + // Something went wrong in this registration. + log.severe("[ClientManager][???] Something went wrong in the client registration process."); + return; + } + + client.setPlayerID(regMessage.getId()); + + // Set other clients to this playerID as well. + regMessage.getClient().setPlayerID(regMessage.getId()); + existingMessage.getClient().setPlayerID(regMessage.getId()); + + + fireClientConnected(client); + + // Remove pending message. + pendingMessages.remove(existingMessage); + clients.add(client); + clientsByClientID.put(client.getClientID(), client); + } + + private void fillInUDPInfo(Client client, ClientRegistrationMessage msg) { + client.setUDPConnection((UDPConnection)msg.getConnection()); + client.setDatagramReceiver(msg.getClient().getDatagramReceiver()); + client.setDatagramChannel(msg.getClient().getDatagramChannel()); + + client.getDatagramChannel().keyFor(msg.getConnection().selector).attach(client); + } + + private void fillInTCPInfo(Client client, ClientRegistrationMessage msg) { + client.setSocketChannel(msg.getClient().getSocketChannel()); + client.setTCPConnection((TCPConnection)msg.getConnection()); + + client.getSocketChannel().keyFor(msg.getConnection().selector).attach(client); + } + + public void addConnectionListener(ConnectionListener listener) { + connectionListeners.add(listener); + } + + public void removeConnectionListener(ConnectionListener listener) { + connectionListeners.remove(listener); + } + + public void clientConnected(Client client) { + } + + public void clientDisconnected(Client client) { + if (clients.contains(client)) { + clients.remove(client); + fireClientDisconnected(client); + } + } + + public void fireClientConnected(Client client) { + for (ConnectionListener listener : connectionListeners) { + listener.clientConnected(client); + } + } + + public void fireClientDisconnected(Client client) { + for (ConnectionListener listener : connectionListeners) { + listener.clientDisconnected(client); + } + } +} diff --git a/engine/src/networking/com/jme3/network/connection/Connection.java b/engine/src/networking/com/jme3/network/connection/Connection.java new file mode 100644 index 000000000..d103f631c --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/Connection.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.network.connection; + +import com.jme3.network.events.ConnectionListener; +import com.jme3.network.events.MessageListener; +import com.jme3.network.message.Message; +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Base class for a connection method. Extend this if you have some other fancy + * way of dealing with connections. This class provides basic message handling, connection filtering + * and handles the selector. + * + * @author Lars Wesselius + */ +public abstract class Connection implements Runnable { + protected String label; + protected Logger log = Logger.getLogger(Connection.class.getName()); + protected final ArrayList connections = new ArrayList(); + + protected Selector selector; + protected boolean alive = false; + protected ArrayList connectorFilters = new ArrayList(); + + protected LinkedList disconnectionQueue = new LinkedList(); + + protected ArrayList connectionListeners = new ArrayList(); + protected ArrayList messageListeners = new ArrayList(); + protected HashMap> individualMessageListeners = new HashMap>(); + + public Connection() { + try { + selector = Selector.open(); + } catch (IOException e) { + log.log(Level.SEVERE, "Could not open selector.", e); + } + } + + /** + * Add a connector filter for this connection. + * + * @param filter The filter to add. + */ + public void addConnectorFilter(ConnectorFilter filter) { + connectorFilters.add(filter); + } + + /** + * Remove a connector filter for this connection. + * @param filter The filter to remove. + */ + public void removeConnectorFilter(ConnectorFilter filter) { + connectorFilters.remove(filter); + } + + /** + * Determine whether this connection should be filtered. + * + * @param address The address that should be checked. + * @return The reason if it should be filtered. + */ + public String shouldFilterConnector(InetSocketAddress address) { + for (ConnectorFilter filter : connectorFilters) { + String str = filter.filterConnector(address); + if (str != null) return str; + } + return null; + } + + public void run() { + if (!alive) alive = true; + try { + if (selector.selectNow() > 0) { + // We've received some keys. + Set keys = selector.selectedKeys(); + + for (Iterator it = keys.iterator(); it.hasNext();) { + SelectionKey key = it.next(); + it.remove(); + + + if (key.isValid() && key.isReadable()) { + read(key.channel()); + } + if (key.isValid() && key.isAcceptable()) { + accept(key.channel()); + } + if (key.isValid() && key.isWritable()) { + write(key.channel()); + } + if (key.isValid() && key.isConnectable()) { + connect(key.channel()); + } + } + } + + // Process queue's. + Client dcClient = disconnectionQueue.poll(); + while (dcClient != null) { + disconnect(dcClient); + dcClient = disconnectionQueue.poll(); + } + } catch (ConnectException ce) { + log.log(Level.WARNING, "[{0}][???] Connection refused.", label); + fireClientDisconnected(null); + } catch (IOException e) { + log.log(Level.SEVERE, "[{0}][???] Error while selecting. Message: {1}", new Object[]{label, e.getMessage()}); + } + } + + /** + * Get all the connectors. + * + * @return A unmodifiable list with the connectors. + */ + public List getLocalConnectors() { + return Collections.unmodifiableList(connections); + } + + /** + * Get the combined connectors, meaning TCP and UDP are combined into one client. + * + * @return A unmodifiable list with the connectors. + */ + public List getConnectors() { + return Collections.unmodifiableList(connections); + } + + /** + * Return whether this connection is still alive. + * + * @return True if so, false if not. + */ + public boolean isAlive() { return alive; } + + /** + * Accept an incoming connection. + * + * @param channel The channel. + * @throws IOException When a problem occurs. + */ + public abstract void accept(SelectableChannel channel) throws IOException; + + /** + * Finish the connection. + * + * @param channel The channel. + * @throws IOException When a problem occurs. + */ + public abstract void connect(SelectableChannel channel) throws IOException; + + /** + * Read from the channel. + * + * @param channel The channel. + * @throws IOException When a problem occurs. + */ + public abstract void read(SelectableChannel channel) throws IOException; + + /** + * Write to a channel. + * + * @param channel The channel to write to. + * @throws IOException When a problem occurs. + */ + public abstract void write(SelectableChannel channel) throws IOException; + + /** + * Connect to a server using this overload. + * + * @param address The address to connect to. + * @throws IOException When a problem occurs. + */ + public abstract void connect(SocketAddress address) throws IOException; + + /** + * Bind to an address. + * + * @param address The address to bind to. + * @throws IOException When a problem occurs. + */ + public abstract void bind(SocketAddress address) throws IOException; + + /** + * Send an object to the server. If this is a server, it will be + * broadcast to all clients. + * + * @param object The object to send. + * @throws IOException When a writing error occurs. + */ + public abstract void sendObject(Object object) throws IOException; + + /** + * Send an object to the connector. Server method. + * + * @param connector The connector to send to. + * @param object The object to send. + * @throws IOException When a writing error occurs. + */ + public abstract void sendObject(Client connector, Object object) throws IOException; + + /** + * Called when the connection implementation should clean up. + * + * @throws IOException When a problem occurs. + */ + public abstract void cleanup() throws IOException; + + /////////////////////////////// Connection management ////////////////////////// + + public void addToDisconnectionQueue(Client client) { + disconnectionQueue.add(client); + } + + /** + * Disconnect a client. + * + * @param client The client to disconnect. + * @throws IOException When closing the client's channel has failed. + */ + private void disconnect(Client client) throws IOException { + if (client == null) return; + + // Find the correct client. + + Client localClient = null; + synchronized (connections){ + for (Client locClient : connections) { + if (locClient.getPlayerID() == client.getPlayerID()) { + localClient = locClient; + break; + } + } + } + + if (localClient == null) localClient = client; + + SocketChannel chan = localClient.getSocketChannel(); + if (chan != null) { + SelectionKey key = chan.keyFor(selector); + if (key != null) key.cancel(); + chan.close(); + } + + synchronized (connections){ + connections.remove(localClient); + } + fireClientDisconnected(client); + } + + + /////////////////////////////// Listener related /////////////////////////////// + + public void addConnectionListener(ConnectionListener listener) { + connectionListeners.add(listener); + } + + public void removeConnectionListener(ConnectionListener listener) { + connectionListeners.remove(listener); + } + + public void addMessageListener(MessageListener listener) { + messageListeners.add(listener); + } + + public void removeMessageListener(MessageListener listener) { + messageListeners.remove(listener); + } + + public void addMessageListener(Class messageClass, MessageListener listener) { + if (individualMessageListeners.containsKey(messageClass)) { + individualMessageListeners.get(messageClass).add(listener); + } else { + List list = new ArrayList(); + list.add(listener); + individualMessageListeners.put(messageClass, list); + } + } + + public void removeMessageListener(Class messageClass, MessageListener listener) { + if (individualMessageListeners.containsKey(messageClass)) { + individualMessageListeners.get(messageClass).remove(listener); + } + } + + // + + @Deprecated + public void addIndividualMessageListener(Class messageClass, MessageListener listener) { + if (individualMessageListeners.containsKey(messageClass)) { + individualMessageListeners.get(messageClass).add(listener); + } else { + List list = new ArrayList(); + list.add(listener); + individualMessageListeners.put(messageClass, list); + } + } + + @Deprecated + public void removeIndividualMessageListener(Class messageClass, MessageListener listener) { + if (individualMessageListeners.containsKey(messageClass)) { + individualMessageListeners.get(messageClass).remove(listener); + } + } + + protected void fireMessageReceived(Message message) { + // Pass to listeners. + for (MessageListener listener : messageListeners) { + listener.messageReceived(message); + } + + List list = individualMessageListeners.get(message.getClass()); + if (list == null) return; + + for (MessageListener listener : list) { + listener.messageReceived(message); + } + } + + protected void fireMessageSent(Message message) { + for (MessageListener listener : messageListeners) { + listener.messageSent(message); + } + + List list = individualMessageListeners.get(message.getClass()); + if (list == null) return; + + for (MessageListener listener : list) { + listener.messageSent(message); + } + } + + protected void fireObjectReceived(Object data) { + for (MessageListener listener : messageListeners) { + listener.objectReceived(data); + } + + if (data == null) return; + + List list = individualMessageListeners.get(data.getClass()); + if (list == null) return; + + for (MessageListener listener : list) { + listener.objectReceived(data); + } + } + + protected void fireObjectSent(Object data) { + for (MessageListener listener : messageListeners) { + listener.objectSent(data); + } + + List list = individualMessageListeners.get(data.getClass()); + if (list == null) return; + + for (MessageListener listener : list) { + listener.objectSent(data); + } + } + + protected void fireClientConnected(Client client) { + for (ConnectionListener listener : connectionListeners) { + listener.clientConnected(client); + } + } + + protected void fireClientDisconnected(Client client) { + for (ConnectionListener listener : connectionListeners) { + listener.clientDisconnected(client); + } + } + + +} diff --git a/engine/src/networking/com/jme3/network/connection/ConnectionRunnable.java b/engine/src/networking/com/jme3/network/connection/ConnectionRunnable.java new file mode 100644 index 000000000..986a3a90a --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/ConnectionRunnable.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.network.connection; + +import com.jme3.system.JmeSystem; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The connection runnable takes the UDP and TCP connections + * and updates them accordingly. + * + * @author Lars Wesselius + */ +public class ConnectionRunnable implements Runnable { + protected Logger log = Logger.getLogger(Server.class.getName()); + + private TCPConnection tcp; + private UDPConnection udp; + private int delay = 2; + private boolean keepAlive = true; + private boolean alive = true; + + public ConnectionRunnable(TCPConnection tcp, UDPConnection udp, int delay) { + this.tcp = tcp; + this.udp = udp; + this.delay = delay; + } + + public ConnectionRunnable(TCPConnection tcp, UDPConnection udp) { + this.tcp = tcp; + this.udp = udp; + } + + public void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public boolean isRunning() { + return alive; + } + + public void run() { + if (!JmeSystem.isLowPermissions()){ + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + log.log(Level.SEVERE, "Uncaught exception thrown in "+thread.toString(), thrown); + } + }); + } + + while (keepAlive) + { + // Run while one of the connections is still live. + tcp.run(); + udp.run(); + + if (delay > 0) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { Thread.yield(); } + } + try + { + tcp.cleanup(); + udp.cleanup(); + } catch (IOException e) { + log.log(Level.WARNING, "[???][???] Could not clean up the connection.", e); + return; + } + alive = false; + log.log(Level.FINE, "[???][???] Cleaned up TCP/UDP."); + } +} diff --git a/engine/src/networking/com/jme3/network/connection/ConnectorFilter.java b/engine/src/networking/com/jme3/network/connection/ConnectorFilter.java new file mode 100644 index 000000000..8334f2e9e --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/ConnectorFilter.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.connection; + +import java.net.InetSocketAddress; + +/** + * A connection filter that can be used by the dev to filter + * connections based on InetAddresses. + * + * @author Lars Wesselius + */ +public interface ConnectorFilter { + + /** + * Filter a connection based on InetAddress. This is called + * every time a client, or Client, connects to the server. + * + * @param address The address. + * @return A null string if the connection should be accepted without problems. + * A non null value indicates the reason of why the client should be dropped. + */ + public String filterConnector(InetSocketAddress address); +} diff --git a/engine/src/networking/com/jme3/network/connection/SSLTCPConnection.java b/engine/src/networking/com/jme3/network/connection/SSLTCPConnection.java new file mode 100644 index 000000000..b52062f95 --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/SSLTCPConnection.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.network.connection; + +import javax.net.ssl.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.logging.Level; + +/** + * The SSLTCPConnection. Handles all SSL traffic for both client + * and server. Please do not use this class, as it does not work. + * Replacement is custom encryption over TCP or UDP, without using SSL. + * + * @author Lars Wesselius + */ +public class SSLTCPConnection extends TCPConnection { + + // Incoming data. Encrypted. + protected ByteBuffer incDataEncrypted; + + // Incoming data. Decrypted. + protected ByteBuffer incDataDecrypted; + + // Outgoing data. Encrypted. + protected ByteBuffer outDataEncrypted; + + // Used for operations that don't consume any data. + protected ByteBuffer dummy; + + protected SSLEngine sslEngine; + protected boolean initialHandshake; + protected SSLEngineResult.HandshakeStatus + handshakeStatus; + protected SSLEngineResult.Status + status; + + protected ArrayList + handshakingConnectors = new ArrayList(); + + public SSLTCPConnection(String name) { + label = name; + createSSLEngine(); + + + SSLSession session = sslEngine.getSession(); + + incDataDecrypted = ByteBuffer.allocateDirect(session.getApplicationBufferSize()); + incDataEncrypted = ByteBuffer.allocateDirect(session.getPacketBufferSize()); + outDataEncrypted = ByteBuffer.allocateDirect(session.getPacketBufferSize()); + + incDataEncrypted.position(incDataEncrypted.limit()); + outDataEncrypted.position(outDataEncrypted.limit()); + dummy = ByteBuffer.allocate(0); + } + + private void createSSLEngine() { + try + { + KeyStore ks = KeyStore.getInstance("JKS"); + File kf = new File("keystore"); + ks.load(new FileInputStream(kf), "lollercopter".toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, "lollercopter".toCharArray()); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + TrustManager[] trustAllCerts = new TrustManager[] + { + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), trustAllCerts, null); + + sslEngine = sslContext.createSSLEngine(); + } catch (Exception e) { + log.log(Level.SEVERE, "[{0}][TCP] Could not create SSL engine: {1}", new Object[]{label, e.getMessage()}); + } + } + + private void doHandshake(SocketChannel channel) throws IOException { + while (true) { + SSLEngineResult result; + log.log(Level.FINEST, "[{0}][TCP] Handshake Status is now {1}.", new Object[]{label, handshakeStatus}); + switch (handshakeStatus) { + case NOT_HANDSHAKING: + log.log(Level.SEVERE, "[{0}][TCP] We're doing a handshake while we're not handshaking.", label); + break; + + case FINISHED: + initialHandshake = false; + channel.keyFor(selector).interestOps(SelectionKey.OP_READ); + + return; + + case NEED_TASK: + // TODO: Run this task in another thread or something. + Runnable task; + while ((task = sslEngine.getDelegatedTask()) != null) { + task.run(); + } + handshakeStatus = sslEngine.getHandshakeStatus(); + break; + + case NEED_UNWRAP: + readAndUnwrap(channel); + + if (initialHandshake && status == SSLEngineResult.Status.BUFFER_UNDERFLOW) { + channel.keyFor(selector).interestOps(SelectionKey.OP_READ); + return; + } + + break; + + case NEED_WRAP: + + if (outDataEncrypted.hasRemaining()) { + log.log(Level.FINE, "[{0}][TCP] We found data that should be written out.", label); + return; + } + + // Prepare to write + outDataEncrypted.clear(); + result = sslEngine.wrap(dummy, outDataEncrypted); + log.log(Level.FINEST, "[{0}][TCP] Wrapping result: {1}.", new Object[]{label, result}); + + if (result.bytesProduced() == 0) log.log(Level.SEVERE, "[{0}][TCP] No net data produced during wrap.", label); + if (result.bytesConsumed() != 0) log.log(Level.SEVERE, "[{0}][TCP] App data consumed during handshake wrap.", label); + handshakeStatus = result.getHandshakeStatus(); + outDataEncrypted.flip(); + + // Now send the data and come back here only when + // the data is all sent + System.out.println("WRITING TO: " + channel + " : " + channel.socket()); + if (!flushData(channel)) { + // There is data left to be send. Wait for it + return; + } + break; + + } + } + } + + public void connect(SocketAddress address) throws IOException { + super.connect(address); + } + + public void bind(SocketAddress address) throws IOException { + super.bind(address); + + sslEngine.setUseClientMode(false); + sslEngine.setNeedClientAuth(false); + } + + public void connect(SelectableChannel channel) throws IOException { + super.connect(channel); + initialHandshake = true; + sslEngine.setUseClientMode(true); + sslEngine.beginHandshake(); + socketChannel.keyFor(selector).interestOps(SelectionKey.OP_WRITE); + handshakeStatus = sslEngine.getHandshakeStatus(); + doHandshake(socketChannel); + } + + public void accept(SelectableChannel channel) throws IOException { + super.accept(channel); + + Client con = connections.get(connections.size() - 1); + handshakingConnectors.add(con); + //con.getChannel().keyFor(selector).interestOps(SelectionKey.OP_WRITE); + + initialHandshake = true; + sslEngine.beginHandshake(); + handshakeStatus = sslEngine.getHandshakeStatus(); + doHandshake(con.getSocketChannel()); + } + + public void read(SelectableChannel channel) throws IOException { + if (initialHandshake) { + doHandshake((SocketChannel)channel); + return; + } + super.read(channel); + } + + public void readAndUnwrap(SocketChannel channel) throws IOException { + incDataEncrypted.flip(); + int bytesRead = channel.read(incDataEncrypted); + + if (bytesRead == 0) { + System.out.println("BUFFER INFO: " + incDataEncrypted); + } + if (bytesRead == -1) { + log.log(Level.FINE, "[{0}][TCP] -1 bytes read, closing stream.", new Object[]{label, bytesRead}); + return; + } + log.log(Level.FINE, "[{0}][TCP] Read {1} bytes.", new Object[]{label, bytesRead}); + + incDataDecrypted.clear(); + incDataEncrypted.flip(); + + SSLEngineResult result; + do { + result = sslEngine.unwrap(incDataEncrypted, incDataDecrypted); + log.log(Level.FINE, "[{0}][TCP] Unwrap result: {1}.", new Object[]{label, result}); + } while (result.getStatus() == SSLEngineResult.Status.OK && + result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP && + result.bytesProduced() == 0); + + // We could have finished the handshake. + if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) { + initialHandshake = false; + channel.keyFor(selector).interestOps(SelectionKey.OP_READ); + } + + // Check if we unwrapped everything there is to unwrap. + if (incDataDecrypted.position() == 0 && + result.getStatus() == SSLEngineResult.Status.OK && incDataEncrypted.hasRemaining()) { + result = sslEngine.unwrap(incDataEncrypted, incDataDecrypted); + log.log(Level.FINE, "[{0}][TCP] Unwrap result: {1}.", new Object[]{label, result}); + } + + // Update statuses + status = result.getStatus(); + handshakeStatus = result.getHandshakeStatus(); + + incDataEncrypted.compact(); + incDataDecrypted.flip(); + + if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || + handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP || + handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED) + { + log.log(Level.FINE, "[{0}][TCP] Rehandshaking..", label); + doHandshake(channel); + } + + } + + public void send(Object object) throws IOException { + super.sendObject(object); + } + + public void send(SocketChannel channel, Object object) throws IOException { + super.send(channel, object); + } + + public void write(SelectableChannel channel) throws IOException { + //super.write(channel); + SocketChannel socketChannel = (SocketChannel)channel; + + if (flushData(socketChannel)) { + if (initialHandshake) { + doHandshake(socketChannel); + } + } + } + + private boolean flushData(SocketChannel channel) throws IOException { + int written = 0; + try { + /// + while (outDataEncrypted.hasRemaining()) { + written += channel.write(outDataEncrypted); + } + } catch (IOException ioe) { + outDataEncrypted.position(outDataEncrypted.limit()); + throw ioe; + } + + log.log(Level.FINE, "[{0}][TCP] Wrote {1} bytes to {2}.", new Object[]{label, written, channel.socket().getRemoteSocketAddress()}); + if (outDataEncrypted.hasRemaining()) { + SelectionKey key = channel.keyFor(selector); + key.interestOps(SelectionKey.OP_WRITE); + return false; + } else { + return true; + } + } + + +} diff --git a/engine/src/networking/com/jme3/network/connection/Server.java b/engine/src/networking/com/jme3/network/connection/Server.java new file mode 100644 index 000000000..a2d126ff3 --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/Server.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.network.connection; + +import com.jme3.network.events.ConnectionListener; +import com.jme3.network.events.MessageListener; +import com.jme3.network.message.ClientRegistrationMessage; +import com.jme3.network.message.DisconnectMessage; +import com.jme3.network.message.Message; +import com.jme3.network.service.ServiceManager; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The class where your SpiderMonkey adventures start. The server class + * manages the TCP and UDP servers. + * + * Using the constructors where you either provide ports or the instances, + * they will bind automatically. If you do not want this to happen, use the + * no arg constructor, and then call bind later on. + * + * @author Lars Wesselius + */ +public class Server extends ServiceManager implements MessageListener { + protected Logger log = Logger.getLogger(Server.class.getName()); + + protected static int serverIDCounter = 0; + protected TCPConnection tcp = null; + protected UDPConnection udp = null; + + protected String label; + protected int serverID; + protected boolean isBound = false; + + protected ConnectionRunnable + thread; + + protected SocketAddress lastUDPAddress; + protected SocketAddress lastTCPAddress; + + protected ClientManager clientManager = new ClientManager(); + + /** + * Default constructor. Sets the label to + * Server#[serverID] + */ + public Server() { + super(ServiceManager.SERVER); + serverID = ++serverIDCounter; + this.label = "Server#" + serverID; + } + + /** + * Constructor providing custom instances of the servers and its addresses. + * + * @param tcp The TCPConnection instance to manage. + * @param udp The UDPConnection instance to manage. + * @param tcpAddress The TCP address to bind to. + * @param udpAddress The UDP address to bind to. + * @throws IOException When a bind error has occurred. + */ + public Server(TCPConnection tcp, UDPConnection udp, SocketAddress tcpAddress, SocketAddress udpAddress) throws IOException { + this(); + + this.tcp = tcp; + tcp.bind(tcpAddress); + lastTCPAddress = tcpAddress; + + this.udp = udp; + udp.bind(udpAddress); + lastUDPAddress = udpAddress; + isBound = true; + + registerInternalListeners(); + } + + /** + * Constructor for providing a TCP server instance. UDP will be disabled. + * + * @param tcp The TCPConnection instance. + * @param tcpAddress The address to bind to. + * @throws IOException When a binding error occurs. + */ + public Server(TCPConnection tcp, SocketAddress tcpAddress) throws IOException { + this(); + + this.tcp = tcp; + tcp.bind(tcpAddress); + lastTCPAddress = tcpAddress; + isBound = true; + + registerInternalListeners(); + } + + /** + * Constructor for providing a UDP server instance. TCP will be disabled. + * + * @param udp The UDP server instance. + * @param udpAddress The address to bind to. + * @throws IOException When a binding error occurs. + */ + public Server(UDPConnection udp, SocketAddress udpAddress) throws IOException { + this(); + + this.udp = udp; + udp.bind(udpAddress); + lastUDPAddress = udpAddress; + isBound = true; + + registerInternalListeners(); + } + + /** + * Simple constructor for providing TCP port and UDP port. Will bind using on + * all interfaces, on given ports. + * + * @param tcpPort The TCP port to use. + * @param udpPort The UDP port to use. + * @throws IOException When a binding error occurs. + */ + public Server(int tcpPort, int udpPort) throws IOException { + this(); + + tcp = new TCPConnection(label); + + lastTCPAddress = new InetSocketAddress(tcpPort); + tcp.bind(lastTCPAddress); + + lastUDPAddress = new InetSocketAddress(udpPort); + udp = new UDPConnection(label); + udp.bind(lastUDPAddress); + isBound = true; + + registerInternalListeners(); + } + + private void registerInternalListeners() { + if (tcp != null) { + tcp.addMessageListener(DisconnectMessage.class, this); + tcp.addMessageListener(ClientRegistrationMessage.class, clientManager); + tcp.addConnectionListener(clientManager); + } + if (udp != null) { + udp.addMessageListener(DisconnectMessage.class, this); + udp.addMessageListener(ClientRegistrationMessage.class, clientManager); + udp.addConnectionListener(clientManager); + } + } + + /** + * Bind method for when the no arg constructor was used. + * + * @param tcpPort The TCP port to use. To turn off, use -1. + * @param udpPort The UDP port to use. To turn off, use -1. + * @throws IllegalArgumentException When an illegal argument was given. + * @throws java.io.IOException When a binding error occurs. + */ + public void bind(int tcpPort, int udpPort) throws IllegalArgumentException, IOException { + if (tcpPort == -1 && udpPort == -1) throw new IllegalArgumentException("No point in binding when you want to turn both the connections off."); + + if (tcpPort != -1) { + lastTCPAddress = new InetSocketAddress(tcpPort); + tcp.bind(lastTCPAddress); + } + if (udpPort != -1) { + lastUDPAddress = new InetSocketAddress(udpPort); + udp.bind(lastUDPAddress); + } + registerInternalListeners(); + isBound = true; + } + + /** + * Broadcast a message. + * + * @param message The message to broadcast. + * @throws IOException When a writing error occurs. + */ + public void broadcast(Message message) throws IOException { + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + if (message.isReliable()) { + if (tcp == null) throw new IOException("No TCP server."); + tcp.sendObject(message); + } else { + if (udp == null) throw new IOException("No UDP server."); + udp.sendObject(message); + } + } + + /** + * Broadcast a message, except to the given client. + * + * @param except The client to refrain from sending the message to. + * @param message The message to send. + * @throws IOException When a writing error occurs. + */ + public void broadcastExcept(Client except, Message message) throws IOException { + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + + // We don't have to check for reliable or not here, since client.send does that. + for (Client con : clientManager.getConnectors()) { + if (con == except) continue; + con.send(message); + } + } + + /** + * Send a TCP message to the given connector. + * + * @param connector The connector to send the message to. + * @param object The object to send. + * @throws IOException When a writing error occurs. + * @deprecated Unnecessary, use client.send(). + */ + @Deprecated + public void sendTCP(Client connector, Object object) throws IOException { + if (tcp == null) throw new IOException("No TCP server."); + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + tcp.sendObject(connector, object); + } + + /** + * Send a UDP message to the given connector. + * + * @param connector The connector to send to. + * @param object The object to send. + * @throws IOException When a writing error occurs. + * @deprecated Unnecessary, use client.send(). + */ + @Deprecated + public void sendUDP(Client connector, Object object) throws IOException { + if (udp == null) throw new IOException("No UDP server."); + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + udp.sendObject(connector, object); + } + + /** + * Broadcast a TCP message. + * + * @param object The message to broadcast. + * @throws IOException When a writing error occurs. + * @deprecated Use broadcast() instead. + */ + @Deprecated + public void broadcastTCP(Object object) throws IOException { + if (tcp == null) throw new IOException("No TCP server."); + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + tcp.sendObject(object); + } + + /** + * Broadcast a UDP message. + * + * @param object The message to broadcast. + * @throws IOException When there's no UDP server initialized, when the server was not bound to a port yet or when a + * write error occurs. + * @deprecated Use broadcast() instead. + */ + @Deprecated + public void broadcastUDP(Object object) throws IOException { + if (udp == null) throw new IOException("No UDP server."); + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + udp.sendObject(object); + } + + /** + * Broadcast a message over TCP, except to the given client. + * + * @param except The client to refrain from sending the object to. + * @param object The object to send. + * @throws IOException When a writing error occurs. + * @deprecated Use broadcastExcept() instead. + */ + @Deprecated + public void broadcastExceptTCP(Client except, Object object) throws IOException { + if (tcp == null) throw new IOException("No TCP server."); + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + for (Client con : tcp.getConnectors()) { + if (con == except) continue; + con.sendTCP(object); + } + } + + /** + * Broadcast a message over UDP, except to the given client. + * + * @param except The client to refrain from sending the object to. + * @param object The object to send. + * @throws IOException When a writing error occurs. + * @deprecated Use broadcastExcept() instead. + */ + @Deprecated + public void broadcastExceptUDP(Client except, Object object) throws IOException { + if (udp == null) throw new IOException("No UDP server."); + if (!isBound) throw new IOException("Not bound yet. Use bind() first."); + for (Client con : udp.getConnectors()) { + if (con == except) continue; + con.sendUDP(object); + } + } + + /** + * Start this server. + * + * @throws IOException When an error occurs. + */ + public void start() throws IOException { + if (!isBound) { + tcp.bind(lastTCPAddress); + udp.bind(lastUDPAddress); + } + new Thread(thread = new ConnectionRunnable(tcp, udp)).start(); + log.log(Level.INFO, "[{0}][???] Started server.", label); + } + + /** + * Start this server with given sleep time. Higher sleep times may affect the system's response time + * negatively, whereas lower values may increase CPU load. Use only when you're certain. + * + * @param sleep The sleep time. + * @throws IOException When an error occurs. + */ + public void start(int sleep) throws IOException { + if (!isBound) { + tcp.bind(lastTCPAddress); + udp.bind(lastUDPAddress); + } + new Thread(thread = new ConnectionRunnable(tcp, udp, sleep)).start(); + log.log(Level.INFO, "[{0}][???] Started server.", label); + } + + /** + * Stop this server. Note that it kicks all clients so that they can + * gracefully quit. + * + * @throws IOException When a writing error occurs. + */ + public void stop() throws IOException { + stop(new DisconnectMessage()); + } + + /** + * Stops the server with custom message. + * + * @throws IOException When a writing error occurs. + */ + public void stop(DisconnectMessage message) throws IOException { + log.log(Level.INFO, "[{0}][???] Server is shutting down..", label); + if (message.getReason() == null) { + message.setReason("Server shut down."); + } + if (message.getType() == null) { + message.setType(DisconnectMessage.KICK); + } + message.setReliable(true); + + broadcast(message); + + for (Client client : getConnectors()) { + tcp.addToDisconnectionQueue(client); + } + + tcp.selector.wakeup(); + log.log(Level.FINE, "[{0}][???] Sent disconnection messages to all clients.", label); + + thread.setKeepAlive(false); + thread = null; + log.log(Level.INFO, "[{0}][???] Server shut down.", label); + isBound = false; + } + + public boolean isRunning() { + return thread != null && thread.isRunning(); + } + + public int getServerID() { + return serverID; + } + + public void setLabel(String label) { + this.label = label; + } + + public String toString() { + return label; + } + + /////////////////////////////// Connection management ////////////////////////// + + /** + * Get all the connectors for the TCP connection. + * + * @return A unmodifiable list with the connectors. + */ + public List getTCPConnectors() { + if (tcp != null) return tcp.getConnectors(); + return null; + } + + /** + * Get all the connectors for the UDP connection. + * + * @return A unmodifiable list with the connectors. + */ + public List getUDPConnectors() { + if (udp != null) return udp.getConnectors(); + return null; + } + + /** + * Get the combined connectors, meaning TCP and UDP are combined into one client. You should + * generally use this for clients. + * + * @return A unmodifiable list with the connectors. + */ + public List getConnectors() { + return clientManager.getConnectors(); + } + + /** + * Get a specific client based on the provided clientID. + * @param clientID The clientID identifying the client requested. + * @return The located client or null if the client was not on the list. + */ + public Client getClientByID(int clientID) { + Client c = clientManager.getClientByClientID(clientID); + return c; + } + + /////////////////////////////// Connector filters ////////////////////////////// + + public void addConnectorFilter(ConnectorFilter filter) { + if (tcp != null) tcp.addConnectorFilter(filter); + if (udp != null) udp.addConnectorFilter(filter); + } + + public void removeConnectorFilter(ConnectorFilter filter) { + if (tcp != null) tcp.removeConnectorFilter(filter); + if (udp != null) udp.removeConnectorFilter(filter); + } + + /////////////////////////////// Listener related /////////////////////////////// + + public void addLocalConnectionListener(ConnectionListener listener) { + if (tcp != null) tcp.addConnectionListener(listener); + if (udp != null) udp.addConnectionListener(listener); + } + + public void removeLocalConnectionListener(ConnectionListener listener) { + if (tcp != null) tcp.removeConnectionListener(listener); + if (udp != null) udp.removeConnectionListener(listener); + } + + public void addConnectionListener(ConnectionListener listener) { + clientManager.addConnectionListener(listener); + } + + public void removeConnectionListener(ConnectionListener listener) { + clientManager.removeConnectionListener(listener); + } + + public void addMessageListener(MessageListener listener) { + if (tcp != null) tcp.addMessageListener(listener); + if (udp != null) udp.addMessageListener(listener); + } + + public void addMessageListener(MessageListener listener, Class... classes) { + for (Class c : classes) { + if (tcp != null) tcp.addMessageListener(c, listener); + if (udp != null) udp.addMessageListener(c, listener); + } + } + + public void removeMessageListener(MessageListener listener) { + if (tcp != null) tcp.removeMessageListener(listener); + if (udp != null) udp.removeMessageListener(listener); + } + + public void removeMessageListener(MessageListener listener, Class... classes) { + for (Class c : classes) { + if (tcp != null) tcp.removeMessageListener(c, listener); + if (udp != null) udp.removeMessageListener(c, listener); + } + } + + /// + + /** + * Add a message listener for a specific class. + * + * @param messageClass The class to listen for. + * @param listener The listener. + * @deprecated Use addMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void addIndividualMessageListener(Class messageClass, MessageListener listener) { + if (tcp != null) tcp.addIndividualMessageListener(messageClass, listener); + if (udp != null) udp.addIndividualMessageListener(messageClass, listener); + } + + /** + * Add a message listener for specific classes. + * + * @param messageClass The classes to listen for. + * @param listener The listener. + * @deprecated Use addMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void addIndividualMessageListener(Class[] messageClass, MessageListener listener) { + for (Class c : messageClass) { + if (tcp != null) tcp.addIndividualMessageListener(c, listener); + if (udp != null) udp.addIndividualMessageListener(c, listener); + } + } + + /** + * Remove a message listener for a specific class. + * + * @param messageClass The class to what this listener is registered. + * @param listener The listener. + * @deprecated Use removeMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void removeIndividualMessageListener(Class messageClass, MessageListener listener) { + if (tcp != null) tcp.removeIndividualMessageListener(messageClass, listener); + if (udp != null) udp.removeIndividualMessageListener(messageClass, listener); + } + + /** + *Remove a message listener for specific classes. + * + * @param messageClass The classes to remove for. + * @param listener The listener. + * @deprecated Use addMessageListener(MessageListener, Class...) instead. + */ + @Deprecated + public void removeIndividualMessageListener(Class[] messageClass, MessageListener listener) { + for (Class c : messageClass) { + if (tcp != null) tcp.removeIndividualMessageListener(c, listener); + if (udp != null) udp.removeIndividualMessageListener(c, listener); + } + } + + public void messageReceived(Message message) { + // Right now, this is definitely a DisconnectMessage. + DisconnectMessage dcMessage = (DisconnectMessage)message; + Client client = dcMessage.getClient(); + + if (clientManager.isClientConnected(client)) { + log.log(Level.INFO, "[{0}][???] Client {1} disconnected ({2}: {3}).", new Object[]{ + label, + client, + dcMessage.getType(), + (dcMessage.getReason() != null) ? dcMessage.getReason() : "No description" + }); + } + dcMessage.getConnection().addToDisconnectionQueue(client); + } + + public void messageSent(Message message) { + + } + + public void objectReceived(Object object) { + + } + + public void objectSent(Object object) { + + } +} diff --git a/engine/src/networking/com/jme3/network/connection/TCPConnection.java b/engine/src/networking/com/jme3/network/connection/TCPConnection.java new file mode 100644 index 000000000..0c9d7cc86 --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/TCPConnection.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.connection; + +import com.jme3.network.message.Message; +import com.jme3.network.queue.MessageQueue; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +/** + * The TCPConnection handles all traffic regarding TCP client and server. + * + * @author Lars Wesselius + * @see Connection + */ +public class TCPConnection extends Connection { + protected SocketChannel socketChannel; + protected ServerSocketChannel serverSocketChannel; + + protected ByteBuffer readBuffer; + protected ByteBuffer writeBuffer; + protected ByteBuffer tempWriteBuffer; + + protected final Object writeLock = new Object(); + + private int objectLength = 0; + + public TCPConnection(String name) + { + label = name; + + readBuffer = ByteBuffer.allocateDirect(16228); + writeBuffer = ByteBuffer.allocateDirect(16228); + tempWriteBuffer = ByteBuffer.allocateDirect(16228); + } + + public TCPConnection() { } + + public void connect(SocketAddress address) throws IOException { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + + socketChannel.configureBlocking(false); + socketChannel.connect(address); + socketChannel.register(selector, SelectionKey.OP_CONNECT).attach(this); + log.log(Level.INFO, "[{1}][TCP] Connecting to {0}", new Object[]{address, label}); + } + + public void bind(SocketAddress address) throws IOException { + serverSocketChannel = selector.provider().openServerSocketChannel(); + serverSocketChannel.socket().bind(address); + serverSocketChannel.configureBlocking(false); + serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + log.log(Level.INFO, "[{1}][TCP] Bound to {0}", new Object[]{address, label}); + } + + public void connect(SelectableChannel channel) throws IOException { + ((SocketChannel)channel).finishConnect(); + socketChannel.keyFor(selector).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + fireClientConnected(null); + log.log(Level.INFO, "[{0}][TCP] Connection succeeded.", label); + } + + public void accept(SelectableChannel channel) throws IOException { + SocketChannel socketChannel = ((ServerSocketChannel)channel).accept(); + + String reason = shouldFilterConnector((InetSocketAddress)socketChannel.socket().getRemoteSocketAddress()); + if (reason != null) { + log.log(Level.INFO, "[{2}][TCP] Client with address {0} got filtered with reason: {1}", new Object[]{(InetSocketAddress)socketChannel.socket().getRemoteSocketAddress(), reason, label}); + socketChannel.close(); + return; + } + + socketChannel.configureBlocking(false); + socketChannel.socket().setTcpNoDelay(true); + + Client con = new Client(true); + con.setTCPConnection(this); + con.setSocketChannel(socketChannel); + + socketChannel.register(selector, SelectionKey.OP_READ, con); + + connections.add(con); + + log.log(Level.INFO, "[{1}][TCP] A client connected with address {0}", new Object[]{socketChannel.socket().getInetAddress(), label}); + } + + public void read(SelectableChannel channel) throws IOException { + SocketChannel socketChannel = (SocketChannel)channel; + + if (socketChannel == null) { + log.log(Level.WARNING, "[{0}][TCP] Connection was closed before we could read.", label); + return; + } + + int read = -1; + readBuffer.compact(); + try { + read = socketChannel.read(readBuffer); + } catch (IOException ioe) { + // Probably a 'remote host closed connection'. + socketChannel.keyFor(selector).cancel(); + + if (serverSocketChannel != null) { + log.log(Level.WARNING, "[{0}][TCP] Connection was forcibly closed before we could read. Disconnected client.", label); + addToDisconnectionQueue((Client)socketChannel.keyFor(selector).attachment()); + } else { + log.log(Level.WARNING, "[{0}][TCP] Server forcibly closed connection. Disconnected.", label); + fireClientDisconnected(null); + } + } + + if (read != -1) { + log.log(Level.FINE, "[{1}][TCP] Read {0} bytes.", new Object[]{read, label}); + } + + readBuffer.flip(); + if (read == -1) { + socketChannel.keyFor(selector).cancel(); + if (serverSocketChannel != null) { + log.log(Level.WARNING, "[{0}][TCP] Connection was closed before we could read. Disconnected client.", label); + addToDisconnectionQueue((Client)socketChannel.keyFor(selector).attachment()); + } else { + log.log(Level.WARNING, "[{0}][TCP] Server closed connection. Disconnected.", label); + fireClientDisconnected(null); + } + return; + } + + // Okay, see if we can read the data length. + while (true) { + try { + + // If we're currently not already reading an object, retrieve the length + // of the next one. + if (objectLength == 0) { + objectLength = readBuffer.getShort(); + } + + int pos = readBuffer.position(); + int oldLimit = readBuffer.limit(); + + int dataLength = objectLength; + if (dataLength > 0 && readBuffer.remaining() >= dataLength) { + // We can read a full object. + if (pos + dataLength + 2 > readBuffer.capacity()) { + readBuffer.limit(readBuffer.capacity()); + } else { + readBuffer.limit(pos + dataLength + 2); + } + Object obj = Serializer.readClassAndObject(readBuffer); + readBuffer.limit(oldLimit); + objectLength = 0; + if (obj != null) { + if (obj instanceof Message) { + Message message = (Message)obj; + + Object attachment = socketChannel.keyFor(selector).attachment(); + if (attachment instanceof Client) message.setClient((Client)attachment); + message.setConnection(this); + this.fireMessageReceived(message); + } else { + this.fireObjectReceived(obj); + } + log.log(Level.FINEST, "[{0}][TCP] Read full object: {1}", new Object[]{label, obj}); + } + } else if (dataLength > readBuffer.remaining()) { + readBuffer.compact(); + int bytesRead = socketChannel.read(readBuffer); + log.log(Level.FINEST, "[{0}][TCP] Object won't fit in buffer, so read {1} more bytes in a compacted buffer.", new Object[]{label, bytesRead}); + readBuffer.flip(); + } else { + objectLength = dataLength; + } + } catch (BufferUnderflowException someEx) { + log.log(Level.FINEST, "[{0}][TCP] Done reading messages.", new Object[]{label}); + break; + } + } + + } + + public void sendObject(Object object) throws IOException { + if (serverSocketChannel == null) { + send(socketChannel, object) ; + } else { + for (Client connector : connections) { + send(connector.getSocketChannel(), object); + } + } + } + + public void sendObject(Client con, Object object) throws IOException { + if (object instanceof Message) ((Message)object).setClient(con); + send(con.getSocketChannel(), object); + } + + public void cleanup() throws IOException { + if (serverSocketChannel != null) { + serverSocketChannel.close(); + connections.clear(); + } else { + socketChannel.close(); + } + } + + protected void send(SocketChannel channel, Object object) throws IOException { + try { + synchronized (writeLock) { + tempWriteBuffer.clear(); + tempWriteBuffer.position(4); + Serializer.writeClassAndObject(tempWriteBuffer, object); + tempWriteBuffer.flip(); + + int dataLength = tempWriteBuffer.limit() - 4; + tempWriteBuffer.position(0); + tempWriteBuffer.putInt(dataLength); + tempWriteBuffer.position(0); + + if (dataLength > writeBuffer.capacity()) { + log.log(Level.WARNING, "[{0}][TCP] Message too big for buffer. Discarded.", label); + return; + } + + if (writeBuffer.position() > 0) { + try { + writeBuffer.put(tempWriteBuffer); + } catch (BufferOverflowException boe) { + log.log(Level.WARNING, "[{0}][TCP] Buffer overflow occurred while appending data to be sent later. " + + "Cleared the buffer, so some data may be lost.", label); + + // TODO The fix here is all wrong. + writeBuffer.clear(); + while (tempWriteBuffer.hasRemaining()) { + if (channel.write(tempWriteBuffer) == 0) break; + + } + } + } else { + int writeLength = 0; + while (tempWriteBuffer.hasRemaining()) { + int wrote = channel.write(tempWriteBuffer); + writeLength += wrote; + if (wrote == 0) { + break; + } + } + + log.log(Level.FINE, "[{1}][TCP] Wrote {0} bytes.", new Object[]{writeLength, label}); + + try + { + if (writeBuffer.hasRemaining()) { + writeBuffer.put(tempWriteBuffer); + channel.keyFor(selector).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + } else { + if (object instanceof Message) { + this.fireMessageSent((Message)object); + } else { + this.fireObjectSent(object); + } + } + } catch (BufferOverflowException boe) { + log.log(Level.WARNING, "[{0}][TCP] Buffer overflow occurred while queuing data to be sent later. " + + "Cleared the buffer, so some data may be lost. Please note that this exception occurs rarely, " + + "so if this is shown often, please check your message sizes or contact the developer.", label); + writeBuffer.clear(); + } + } + } + } catch (IOException ioe) { + // We're doing some additional handling here, since a client could be reset. + Client client; + if (socketChannel == null) { + client = ((Message)object).getClient(); + } else { + client = (Client)socketChannel.keyFor(selector).attachment(); + } + if (client != null) { + addToDisconnectionQueue(client); + + log.log(Level.WARNING, "[{0}][TCP] Disconnected {1} because an error occurred: {2}.", new Object[]{label, client, ioe.getMessage()}); + return; + } + throw ioe; + } + } + + public synchronized void write(SelectableChannel channel) throws IOException { + SocketChannel socketChannel = (SocketChannel)channel; + Client client = (Client)socketChannel.keyFor(selector).attachment(); + MessageQueue queue = client.getMessageQueue(); + + Map sizeMap = new LinkedHashMap(); + for (Iterator it = queue.iterator(); it.hasNext();) { + Message message = it.next(); + if (!message.isReliable()) continue; + + int pos = writeBuffer.position(); + try { + writeBuffer.position(pos + 2); + Serializer.writeClassAndObject(writeBuffer, message); + + + short dataLength = (short)(writeBuffer.position() - pos - 2); + + writeBuffer.position(pos); + writeBuffer.putShort(dataLength); + writeBuffer.position(pos + dataLength + 2); + + sizeMap.put(message, dataLength); + + it.remove(); + } catch (Exception bfe) { + // No problem, just write the buffer and be done with it. + writeBuffer.position(pos); + break; + } + } + + writeBuffer.flip(); + + int written = 0; + while (writeBuffer.hasRemaining()) { + int wrote = socketChannel.write(writeBuffer); + written += wrote; + + if (wrote == 0) { + break; + } + } + + log.log(Level.FINE, "[{1}][TCP] Wrote {0} bytes.", new Object[]{written, label}); + + // Check which messages were NOT sent. + if (writeBuffer.hasRemaining()) { + for (Iterator> it = sizeMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + + written -= entry.getValue(); + if (written > 0) { + it.remove(); + } else { + // Re add to queue. + client.getMessageQueue().add(entry.getKey()); + } + } + } + + if (queue.isEmpty()) { + channel.keyFor(selector).interestOps(SelectionKey.OP_READ); + } + writeBuffer.clear(); + } +} diff --git a/engine/src/networking/com/jme3/network/connection/UDPConnection.java b/engine/src/networking/com/jme3/network/connection/UDPConnection.java new file mode 100644 index 000000000..5505d9e80 --- /dev/null +++ b/engine/src/networking/com/jme3/network/connection/UDPConnection.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.connection; + +import com.jme3.network.message.DiscoverHostMessage; +import com.jme3.network.message.Message; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.util.logging.Level; + +/** + * The UDPConnection handles all UDP traffic. + * + * @author Lars Wesselius + */ +public class UDPConnection extends Connection { + protected DatagramChannel datagramChannel; + + protected ByteBuffer writeBuffer; + protected ByteBuffer readBuffer; + + protected SocketAddress target = null; + + public UDPConnection(String label) { + this.label = label; + + readBuffer = ByteBuffer.allocateDirect(8192); + writeBuffer = ByteBuffer.allocateDirect(8192); + } + + public void connect(SocketAddress address) throws IOException { + datagramChannel = selector.provider().openDatagramChannel(); + datagramChannel.socket().bind(null); + datagramChannel.socket().connect(address); + datagramChannel.configureBlocking(false); + + datagramChannel.register(selector, SelectionKey.OP_READ); + log.log(Level.INFO, "[{1}][UDP] Set target to {0}", new Object[]{address, label}); + target = address; + } + + public void bind(SocketAddress address) throws IOException { + datagramChannel = selector.provider().openDatagramChannel(); + datagramChannel.socket().bind(address); + datagramChannel.configureBlocking(false); + + datagramChannel.register(selector, SelectionKey.OP_READ); + + log.log(Level.INFO, "[{1}][UDP] Bound to {0}", new Object[]{address, label}); + } + + public void connect(SelectableChannel channel) throws IOException { + // UDP is connectionless. + } + + public void accept(SelectableChannel channel) throws IOException { + // UDP is connectionless. + } + + public void read(SelectableChannel channel) throws IOException { + DatagramChannel socketChannel = (DatagramChannel)channel; + + InetSocketAddress address = (InetSocketAddress)datagramChannel.receive(readBuffer); + if (address == null){ + //System.out.println("Address is NULL!"); + //TODO: Fix disconnection issue + socketChannel.close(); + + return; + } + + String reason = shouldFilterConnector(address); + if (reason != null) { + log.log(Level.INFO, "[Server][UDP] Client with address {0} got filtered with reason: {1}", new Object[]{address, reason}); + socketChannel.close(); + return; + } + + SelectionKey key = socketChannel.keyFor(selector); + if ((key.attachment() == null || ((Client)key.attachment()).getDatagramReceiver() != address) && target == null) { + Client client = new Client(true); + client.setDatagramReceiver(address); + client.setUDPConnection(this); + client.setDatagramChannel(socketChannel); + synchronized (connections){ + connections.add(client); + } + + key.attach(client); + } + + readBuffer.flip(); + + + Object object = Serializer.readClassAndObject(readBuffer); + + log.log(Level.FINE, "[{0}][UDP] Read full object: {1}", new Object[]{label, object}); + + if (object instanceof Message) { + Message message = (Message)object; + + if (message instanceof DiscoverHostMessage) { + synchronized (connections){ + connections.remove( (Client) key.attachment() ); + } + log.log(Level.FINE, "[{0}][UDP] Responded to a discover host message by {1}.", new Object[]{label, address}); + send(address, message); + return; + } + + Object attachment = socketChannel.keyFor(selector).attachment(); + if (attachment instanceof Client) message.setClient((Client)attachment); + message.setConnection(this); + this.fireMessageReceived(message); + } else { + this.fireObjectReceived(object); + } + + readBuffer.clear(); + } + + protected void send(SocketAddress dest, Object object) { + try { + Serializer.writeClassAndObject(writeBuffer, object); + writeBuffer.flip(); + + if (dest == null) + throw new NullPointerException(); + + int bytes = datagramChannel.send(writeBuffer, dest); + + if (object instanceof Message) { + this.fireMessageSent((Message)object); + } else { + this.fireObjectSent(object); + } + + log.log(Level.FINE, "[{0}][UDP] Wrote {1} bytes to {2}.", new Object[]{label, bytes, dest}); + writeBuffer.clear(); + } catch (ClosedChannelException e) { + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void sendObject(Object object) throws IOException { + if (target == null) { + // This is a UDP server. + synchronized (connections){ + for (Client connector : connections) { + send(connector.getDatagramReceiver(), object); + } + } + } else { + send(target, object); + } + } + + public void sendObject(Client client, Object object) throws IOException { + if (object instanceof Message) ((Message)object).setClient(client); + send(client.getDatagramReceiver(), object); + } + + public void cleanup() throws IOException { + datagramChannel.close(); + + if (target == null) { + synchronized (connections){ + connections.clear(); + } + } + } + + public void write(SelectableChannel channel) throws IOException { + // UDP is (almost) always ready for data, so send() will do. + } +} diff --git a/engine/src/networking/com/jme3/network/events/ConnectionAdapter.java b/engine/src/networking/com/jme3/network/events/ConnectionAdapter.java new file mode 100644 index 000000000..9ec29318a --- /dev/null +++ b/engine/src/networking/com/jme3/network/events/ConnectionAdapter.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.network.events; + +import com.jme3.network.connection.Client; + +/** + * Server adapter for making it easier to listen for server events. + * + * @author Lars Wesselius + */ +public class ConnectionAdapter implements ConnectionListener { + public void clientConnected(Client client) {} + public void clientDisconnected(Client client) {} +} diff --git a/engine/src/networking/com/jme3/network/events/ConnectionListener.java b/engine/src/networking/com/jme3/network/events/ConnectionListener.java new file mode 100644 index 000000000..a2b8ff672 --- /dev/null +++ b/engine/src/networking/com/jme3/network/events/ConnectionListener.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.network.events; + +import com.jme3.network.connection.Client; + +/** + * Listener for server events. + * + * @author Lars Wesselius + */ +public interface ConnectionListener { + public void clientConnected(Client client); + public void clientDisconnected(Client client); +} diff --git a/engine/src/networking/com/jme3/network/events/MessageAdapter.java b/engine/src/networking/com/jme3/network/events/MessageAdapter.java new file mode 100644 index 000000000..646f25b1a --- /dev/null +++ b/engine/src/networking/com/jme3/network/events/MessageAdapter.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.network.events; + +import com.jme3.network.message.Message; + +/** + * Message adapter to make it easier to listen to message vents. + * + * @author Lars Wesselius + */ +public class MessageAdapter implements MessageListener { + public void messageReceived(Message message) {} + public void messageSent(Message message) {} + public void objectReceived(Object object) {} + public void objectSent(Object object) {} +} diff --git a/engine/src/networking/com/jme3/network/events/MessageListener.java b/engine/src/networking/com/jme3/network/events/MessageListener.java new file mode 100644 index 000000000..232aabefc --- /dev/null +++ b/engine/src/networking/com/jme3/network/events/MessageListener.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.network.events; + +import com.jme3.network.message.Message; + +/** + * Listener for messages. + * + * @author Lars Wesselius + */ +public interface MessageListener { + public void messageReceived(Message message); + public void messageSent(Message message); + + public void objectReceived(Object object); + public void objectSent(Object object); +} 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 000000000..b8a3df04e --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/ClientRegistrationMessage.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 com.jme3.network.message; + +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 Message { + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } +} 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 000000000..f4fd70d83 --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/CompressedMessage.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.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 Message { + 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 000000000..415752ebf --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/DisconnectMessage.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.message; + +import com.jme3.network.serializing.Serializable; + +/** + * Represents a disconnect message. + * + * @author Lars Wesselius + */ +@Serializable() +public class DisconnectMessage extends Message { + 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/DiscoverHostMessage.java b/engine/src/networking/com/jme3/network/message/DiscoverHostMessage.java new file mode 100644 index 000000000..4636d8404 --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/DiscoverHostMessage.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.network.message; + +import com.jme3.network.serializing.Serializable; + +/** + * The discover host message. + * + * @author Lars Wesselius + */ +@Serializable() +public class DiscoverHostMessage extends Message { +} 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 000000000..6512949e5 --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/GZIPCompressedMessage.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.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/Message.java b/engine/src/networking/com/jme3/network/message/Message.java new file mode 100644 index 000000000..c0dc85286 --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/Message.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.network.message; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Connection; +import com.jme3.network.serializing.Serializable; + +/** + * Message represents data being sent to the other side. This can be anything, + * and it will be serialized field by field. Extend this class if you wish to + * provide objects with common fields to the other side. + * + * @author Lars Wesselius + */ +@Serializable() +public class Message { + // The connector this message is meant for. + private transient Client connector; + private transient Connection connection; + private transient boolean reliable = true; + + /** + * @deprecated Do NOT use anymore. No alternative, since it's set automatically. + */ + public Message(Connection connection) { + this.connection = connection; + } + + public Message() {} + + public Message(boolean reliable) { + this.reliable = reliable; + } + + public boolean isReliable() { + return reliable; + } + + public Message setReliable(boolean reliable) { + this.reliable = reliable; + return this; + } + + public Client getClient() { + return connector; + } + + public void setClient(Client connector) { + this.connector = connector; + } + + public Connection getConnection() { + return connection; + } + + public void setConnection(Connection connection) { + this.connection = connection; + } +} diff --git a/engine/src/networking/com/jme3/network/message/StreamDataMessage.java b/engine/src/networking/com/jme3/network/message/StreamDataMessage.java new file mode 100644 index 000000000..963a5df61 --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/StreamDataMessage.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.message; + +import com.jme3.network.serializing.Serializable; + +/** + * Stream message contains the data for the stream. + * + * @author Lars Wesselius + */ +@Serializable() +public class StreamDataMessage extends StreamMessage { + private byte[] data; + + public StreamDataMessage() { } + + public StreamDataMessage(short id) { + super(id); + } + + public void setData(byte[] data) { + this.data = data; + } + + public byte[] getData() { return data; } + +} diff --git a/engine/src/networking/com/jme3/network/message/StreamMessage.java b/engine/src/networking/com/jme3/network/message/StreamMessage.java new file mode 100644 index 000000000..40f6b8c35 --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/StreamMessage.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.message; + +import com.jme3.network.serializing.Serializable; + +@Serializable() +public class StreamMessage extends Message { + private short streamID; + + public StreamMessage(short id) { + streamID = id; + } + + public StreamMessage() {} + + public short getStreamID() { + return streamID; + } + + public void setStreamID(short streamID) { + this.streamID = streamID; + } +} 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 000000000..dcd89713c --- /dev/null +++ b/engine/src/networking/com/jme3/network/message/ZIPCompressedMessage.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.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/queue/MessageQueue.java b/engine/src/networking/com/jme3/network/queue/MessageQueue.java new file mode 100644 index 000000000..8c0e7abff --- /dev/null +++ b/engine/src/networking/com/jme3/network/queue/MessageQueue.java @@ -0,0 +1,8 @@ +package com.jme3.network.queue; + +import com.jme3.network.message.Message; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class MessageQueue extends ConcurrentLinkedQueue { + +} 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 000000000..a6477aafe --- /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 000000000..4bcd8bc77 --- /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 + */ +class MethodDef { + + /** + * Method name + */ + String name; + + /** + * Return type + */ + Class retType; + + /** + * Parameter types + */ + 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 000000000..2e3e18cd2 --- /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 +class ObjectDef { + + /** + * The object name, can be null if undefined. + */ + String objectName; + + /** + * Object ID + */ + int objectId; + + /** + * Methods of the implementation on the local client. Set to null + * on remote clients. + */ + Method[] methods; + + /** + * Method definitions of the implementation. Set to null on + * the local client. + */ + MethodDef[] methodDefs; + + @Override + public String toString(){ + return "ObjectDef[name=" + objectName + ", ID=" + 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 000000000..82b351c7d --- /dev/null +++ b/engine/src/networking/com/jme3/network/rmi/ObjectStore.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.ConnectionListener; +import com.jme3.network.events.MessageListener; +import com.jme3.network.message.Message; +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.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; + +public class ObjectStore implements MessageListener, ConnectionListener { + + private static final class Invocation { + Object retVal; + boolean available = false; + } + + private Client client; + private Server server; + + // Local object ID counter + private short objectIdCounter = 0; + + // Local invocation ID counter + private short invocationIdCounter = 0; + + // Invocations waiting .. + private IntMap pendingInvocations = new IntMap(); + + // Objects I share with other people + private IntMap localObjects = new IntMap(); + + // Objects others share with me + private HashMap remoteObjects = new HashMap(); + private IntMap remoteObjectsById = new IntMap(); + + private final Object receiveObjectLock = new Object(); + + 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(this, RemoteObjectDefMessage.class, + RemoteMethodCallMessage.class, + RemoteMethodReturnMessage.class); + client.addConnectionListener(this); + } + + public ObjectStore(Server server){ + this.server = server; + server.addMessageListener(this, RemoteObjectDefMessage.class, + RemoteMethodCallMessage.class, + RemoteMethodReturnMessage.class); + server.addConnectionListener(this); + } + + 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(); + + // 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); + else + server.broadcast(defMsg); + } + + public T getExposedObject(String name, Class 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(); + pendingInvocations.put(call.invocationId, invoke); + } + + try{ + if (server != null){ + remoteObj.client.send(call); + }else{ + client.send(call); + } + } catch (IOException ex){ + ex.printStackTrace(); + } + + if (invoke != null){ + synchronized(invoke){ + while (!invoke.available){ + try { + invoke.wait(); + } catch (InterruptedException ex){ + ex.printStackTrace(); + } + } + } + pendingInvocations.remove(call.invocationId); + return invoke.retVal; + }else{ + return null; + } + } + + public void messageReceived(Message message) { + if (message instanceof RemoteObjectDefMessage){ + RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message; + + ObjectDef[] defs = defMsg.objects; + for (ObjectDef def : defs){ + RemoteObject remoteObject = new RemoteObject(this, message.getClient()); + 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); + + Object obj = localObj.theObject; + Method method = localObj.methods[call.methodId]; + Object[] args = call.args; + Object ret; + try { + ret = method.invoke(obj, args); + } catch (Exception ex){ + throw new RuntimeException(ex); + } + + if (method.getReturnType() != void.class){ + // send return value back + RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage(); + retMsg.invocationID = invocationIdCounter++; + retMsg.retVal = ret; + try { + if (server != null){ + call.getClient().send(retMsg); + } else{ + client.send(retMsg); + } + } catch (IOException ex){ + ex.printStackTrace(); + } + } + }else if (message instanceof RemoteMethodReturnMessage){ + RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message; + Invocation invoke = pendingInvocations.get(retMsg.invocationID); + if (invoke == null){ + throw new RuntimeException("Cannot find invocation ID: " + retMsg.invocationID); + } + + synchronized (invoke){ + invoke.retVal = retMsg.retVal; + invoke.available = true; + invoke.notifyAll(); + } + } + } + + public void clientConnected(Client client) { + if (localObjects.size() > 0){ + // send a object definition message + ObjectDef[] defs = new ObjectDef[localObjects.size()]; + int i = 0; + for (Entry entry : localObjects){ + defs[i] = makeObjectDef(entry.getValue()); + i++; + } + + RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); + defMsg.objects = defs; + try { + if (this.client != null){ + this.client.send(defMsg); + } else{ + client.send(defMsg); + } + } catch (IOException ex){ + ex.printStackTrace(); + } + } + } + + public void clientDisconnected(Client client) { + + } + + public void messageSent(Message message) { + } + + public void objectReceived(Object object) { + } + + public void objectSent(Object object) { + } + +} 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 000000000..9feaafc30 --- /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.message.Message; +import com.jme3.network.serializing.Serializable; + +/** + * Sent to a remote client to make a remote method invocation. + * + * @author Kirill Vainer + */ +@Serializable +class RemoteMethodCallMessage extends Message { + + public RemoteMethodCallMessage(){ + super(true); + } + + /** + * The object ID on which the call is being made. + */ + int objectId; + + /** + * The method ID used for look-up in the LocalObject.methods array. + */ + 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. + */ + short invocationId; + + /** + * Arguments of the remote method invocation. + */ + Object[] args; + + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append("MethodCall[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 000000000..1890bb527 --- /dev/null +++ b/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.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.rmi; + + +import com.jme3.network.message.Message; +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 +class RemoteMethodReturnMessage extends Message { + + public RemoteMethodReturnMessage(){ + super(true); + } + + /** + * Invocation ID that was set in the {@link RemoteMethodCallMessage}. + */ + short invocationID; + + /** + * The return value, could be null. + */ + Object retVal; + + + @Override + public String toString(){ + return "MethodReturn[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 000000000..702d70a7b --- /dev/null +++ b/engine/src/networking/com/jme3/network/rmi/RemoteObject.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.network.rmi; + + +import com.jme3.network.connection.Client; +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 methodMap = new HashMap(); + + /** + * The {@link ObjectStore} which stores this RMI interface. + */ + ObjectStore store; + + /** + * The client who exposed the RMI interface, or null if the server + * exposed it. + */ + Client client; + + public RemoteObject(ObjectStore store, Client 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> nameToMethods + = new HashMap>(); + + for (Method method : interfaceClass.getDeclaredMethods()){ + ArrayList list = nameToMethods.get(method.getName()); + if (list == null){ + list = new ArrayList(); + nameToMethods.put(method.getName(), list); + } + list.add(method); + } + + mapping_search: for (int i = 0; i < methodDefs.length; i++){ + MethodDef methodDef = methodDefs[i]; + ArrayList 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 000000000..55127f6ea --- /dev/null +++ b/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.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.rmi; + + + +import com.jme3.network.message.Message; +import com.jme3.network.serializing.Serializable; + +/** + * Sent to expose RMI interfaces on the local client to other clients. + * @author Kirill Vainer + */ +@Serializable +class RemoteObjectDefMessage extends Message { + + ObjectDef[] objects; + + public RemoteObjectDefMessage(){ + super(true); + } + +} 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 000000000..81a612925 --- /dev/null +++ b/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +/** + * {@link RmiSerializer} is responsible for serializing RMI messages + * like define object, call, and return. + * + * @author Kirill Vainer + */ +public class RmiSerializer extends Serializer { + + private char[] chrBuf = new char[256]; + + private void writeString(ByteBuffer buffer, String string) throws IOException{ + int length = string.length(); + if (length > 255){ + throw new IOException("Cannot serialize: "+ string + "\nToo long!"); + } + + 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) + throw new IOException("Unknown class: "+clazz); + + 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 + throw new IOException("Undefined class ID: " + id); + } + 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); + 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 readObject(ByteBuffer data, Class 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 000000000..751084e80 --- /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 000000000..d17946155 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/Serializer.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.*; +import com.jme3.network.serializing.serializers.*; +import java.awt.RenderingHints; +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.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 Map idRegistrations = new HashMap(); + private static final Map classRegistrations = new HashMap(); + + 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; + + + + // 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(RenderingHints.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(Message.class); + registerClass(DisconnectMessage.class); + registerClass(ClientRegistrationMessage.class); + registerClass(DiscoverHostMessage.class); + registerClass(StreamDataMessage.class); + registerClass(StreamMessage.class); + } + + public static SerializerRegistration registerClass(Class cls) { + 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); + + 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); + + return reg; + } + return null; + } + + public static SerializerRegistration[] registerPackage(String pkgName) { + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + String path = pkgName.replace('.', '/'); + Enumeration resources = classLoader.getResources(path); + List dirs = new ArrayList(); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + dirs.add(new File(resource.getFile())); + } + ArrayList classes = new ArrayList(); + 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); + } + return registeredClasses; + } catch (Exception e) { + e.printStackTrace(); + } + return new SerializerRegistration[0]; + } + + private static List findClasses(File dir, String pkgName) throws ClassNotFoundException { + List classes = new ArrayList(); + 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 = --nextId; + if (existingReg != null) id = existingReg.getId(); + SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); + + idRegistrations.put(id, reg); + classRegistrations.put(cls, reg); + + serializer.initialize(cls); + + return reg; + } + + public static Serializer getExactSerializer(Class cls) { + return classRegistrations.get(cls).getSerializer(); + } + + public static Serializer getSerializer(Class cls) { + return getSerializerRegistration(cls).getSerializer(); + } + + public static SerializerRegistration getExactSerializerRegistration(Class cls) { + return classRegistrations.get(cls); + } + + public static SerializerRegistration getSerializerRegistration(Class cls) { + SerializerRegistration reg = classRegistrations.get(cls); + + if (reg != null) return reg; + + for (Map.Entry 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); + 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; + 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. + */ + public static Object readClassAndObject(ByteBuffer buffer) throws IOException { + SerializerRegistration reg = readClass(buffer); + if (reg == null) return null; + 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) { + SerializerRegistration reg = getSerializerRegistration(type); + if (reg == null) { + reg = classRegistrations.get(Message.class); + //registerClassToSerializer(type, FieldSerializer.class); + } + 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 readObject(ByteBuffer data, Class 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/SerializerRegistration.java b/engine/src/networking/com/jme3/network/serializing/SerializerRegistration.java new file mode 100644 index 000000000..22dd71187 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/SerializerRegistration.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.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; + } +} 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 000000000..6a9049d06 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/ArraySerializer.java @@ -0,0 +1,154 @@ +/* + * 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 + */ +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 readObject(ByteBuffer data, Class 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 000000000..d6b8a9ee9 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/BooleanSerializer.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; + +/** + * Boolean serializer. + * + * @author Lars Wesselius + */ +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 000000000..09ac6fab6 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/ByteSerializer.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.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Byte serializer. + * + * @author Lars Wesselius + */ +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 000000000..d51b88ca8 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/CharSerializer.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.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Char serializer. + * + * @author Lars Wesselius + */ +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 000000000..3713a12f4 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/CollectionSerializer.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 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 { + + public T readObject(ByteBuffer data, Class 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 000000000..04fe383d7 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/DateSerializer.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; +import java.util.Date; + +/** + * Date serializer. + * + * @author Lars Wesselius + */ +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 000000000..8427c1d35 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/DoubleSerializer.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.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Double serializer. + * + * @author Lars Wesselius + */ +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 000000000..e3d87e8e0 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/EnumSerializer.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.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Enum serializer. + * + * @author Lars Wesselius + */ +public class EnumSerializer extends Serializer { + public T readObject(ByteBuffer data, Class c) throws IOException { + try { + int ordinal = data.getInt(); + + if (ordinal == -1) return null; + T[] enumConstants = c.getEnumConstants(); + 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 000000000..05ce82a06 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/FieldSerializer.java @@ -0,0 +1,152 @@ +/* + * 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.message.Message; +import com.jme3.network.serializing.Serializer; +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 savedFields = new HashMap(); + + public void initialize(Class clazz) { + List fields = new ArrayList(); + + Class processingClass = clazz; + while (processingClass != Object.class && processingClass != Message.class) { + Collections.addAll(fields, processingClass.getDeclaredFields()); + processingClass = processingClass.getSuperclass(); + } + + List cachedFields = new ArrayList(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())) cachedField.serializer = Serializer.getSerializer(field.getType()); + + cachedFields.add(cachedField); + } + + Collections.sort(cachedFields, new Comparator() { + public int compare (SavedField o1, SavedField o2) { + return o1.field.getName().compareTo(o2.field.getName()); + } + }); + savedFields.put(clazz, cachedFields.toArray(new SavedField[cachedFields.size()])); + + + } + + public T readObject(ByteBuffer data, Class c) throws IOException { + SavedField[] fields = savedFields.get(c); + + T object; + try { + object = c.newInstance(); + } catch (Exception e) { + throw new IOException(e.toString()); + } + + 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 IOException(e.toString()); + } + } + return object; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + 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) { + log.log(Level.WARNING, "[FieldSerializer][???] Exception occurred on writing. Maybe you've forgotten to register a class, or maybe a class member does not have a serializer."); + throw new IOException(e.toString()); + } + } + } + + 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 000000000..b9cfad980 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/FloatSerializer.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.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Float serializer. + * + * @author Lars Wesselius + */ +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 000000000..6208870d3 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/GZIPSerializer.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 com.jme3.network.serializing.serializers; + +import com.jme3.network.message.GZIPCompressedMessage; +import com.jme3.network.message.Message; +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 { + + public T readObject(ByteBuffer data, Class 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 000000000..7430410a1 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/IntSerializer.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.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 + */ +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 000000000..a10939f0f --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/LongSerializer.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.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * The Long serializer. + * + * @author Lars Wesselius + */ +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 000000000..7e96a0e06 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/MapSerializer.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 + } + } + + */ + + public T readObject(ByteBuffer data, Class 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; + } + + 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 entries = map.entrySet(); + + Iterator 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 000000000..577b27a30 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/SavableSerializer.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.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 + public T readObject(ByteBuffer data, Class 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 000000000..d295549e0 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/SerializableSerializer.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.io.Serializable; +import java.nio.ByteBuffer; + +/** + * Serializes uses Java built-in method. + * + * TODO + * @author Lars Wesselius + */ +public class SerializableSerializer extends Serializer { + public Serializable readObject(ByteBuffer data, Class c) throws IOException { + return null; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + } +} 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 000000000..355875425 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/ShortSerializer.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.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Short serializer. + * + * @author Lars Wesselius + */ +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 000000000..98c183fe7 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/StringSerializer.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 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 + */ +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 000000000..1162211b9 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/Vector3Serializer.java @@ -0,0 +1,26 @@ +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 + */ +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 000000000..eed6a3064 --- /dev/null +++ b/engine/src/networking/com/jme3/network/serializing/serializers/ZIPSerializer.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.network.serializing.serializers; + +import com.jme3.network.message.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 { + + public T readObject(ByteBuffer data, Class 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/networking/com/jme3/network/service/Service.java b/engine/src/networking/com/jme3/network/service/Service.java new file mode 100644 index 000000000..5f9783b20 --- /dev/null +++ b/engine/src/networking/com/jme3/network/service/Service.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.service; + +/** + * The Service interface. All services should implement this class, to provide a common way to manage service. + * + * @author Lars Wesselius + */ +public interface Service { +} diff --git a/engine/src/networking/com/jme3/network/service/ServiceManager.java b/engine/src/networking/com/jme3/network/service/ServiceManager.java new file mode 100644 index 000000000..a827b29d2 --- /dev/null +++ b/engine/src/networking/com/jme3/network/service/ServiceManager.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.network.service; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A class can extend Service Manager to support services. + * + * @author Lars Wesselius + */ +public class ServiceManager { + private Logger log = Logger.getLogger(ServiceManager.class.getName()); + private final List services = new ArrayList(); + + private boolean client; + + public static final boolean CLIENT = true; + public static final boolean SERVER = false; + + public ServiceManager(boolean client) { + this.client = client; + } + + public T getService(Class cls) { + for (Service service : services) { + if (service.getClass() == cls) return (T)service; + } + + try { + if (!Service.class.isAssignableFrom(cls)) + return null; + + Constructor ctor; + if (client) { + try { + ctor = cls.getConstructor(new Class[]{Client.class}); + } catch (NoSuchMethodException nsme) { + log.log(Level.WARNING, "[ServiceManager][???] The service {0} does not support client mode.", cls); + return null; + } + } else { + try { + ctor = cls.getConstructor(new Class[]{Server.class}); + } catch (NoSuchMethodException nsme) { + log.log(Level.WARNING, "[ServiceManager][???] The service {0} does not support server mode.", cls); + return null; + } + } + + T inst = (T)ctor.newInstance(this); + + services.add((Service)inst); + return inst; + } catch (Exception e) { + log.log(Level.SEVERE, "[ServiceManager][???] Instantiaton of service failed.", e); + } + return null; + } +} diff --git a/engine/src/networking/com/jme3/network/streaming/ClientStreamingService.java b/engine/src/networking/com/jme3/network/streaming/ClientStreamingService.java new file mode 100644 index 000000000..db34e90e2 --- /dev/null +++ b/engine/src/networking/com/jme3/network/streaming/ClientStreamingService.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.network.streaming; + +import com.jme3.network.connection.Client; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.Message; +import com.jme3.network.message.StreamDataMessage; +import com.jme3.network.message.StreamMessage; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ClientStreamingService extends MessageAdapter { + private static Logger log = Logger.getLogger(StreamingService.class.getName()); + + protected ArrayList + streamListeners; + + protected ArrayList streams; + + private Client client; + + + public ClientStreamingService(Client client) { + this.client = client; + streams = new ArrayList(); + streamListeners = new ArrayList(); + client.addMessageListener(this, StreamDataMessage.class, + StreamMessage.class); + } + + // Client classes/methods ////////////////////////////////////////////////////////////// + + public void addStreamListener(StreamListener listener) { + streamListeners.add(listener); + } + + public void removeStreamListener(StreamListener listener) { + streamListeners.remove(listener); + } + + public void messageReceived(Message message) { + if (message instanceof StreamMessage && !(message instanceof StreamDataMessage)) { + // A stream was offered. + StreamMessage msg = (StreamMessage)message; + Stream stream = getStream(msg.getStreamID()); + + if (stream != null) { + // This is a completion message. + for (StreamListener listener : stream.getDataListeners()) { + listener.streamCompleted(msg); + } + } else { + stream = new Stream(); + stream.setMessage(msg); + boolean accept = fireStreamOffered(stream, msg); + + streams.add(stream); + if (accept) { + try { + client.send(msg); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } else if (message instanceof StreamDataMessage) { + StreamDataMessage dataMessage = (StreamDataMessage)message; + Stream stream = getStream(dataMessage.getStreamID()); + if (stream == null) { + log.log(Level.WARNING, "[StreamClient][TCP] We've received a data message even though we didn't register to the stream."); + return; + } + + for (StreamListener listener : stream.getDataListeners()) { + listener.streamDataReceived(dataMessage); + } + } + + } + + private Stream getStream(short id) { + for (Stream stream : streams) { + if (stream.getMessage().getStreamID() == id) return stream; + } + return null; + } + + private boolean fireStreamOffered(Stream stream, StreamMessage message) { + boolean accept = false; + for (StreamListener listener : streamListeners) { + if (listener.streamOffered(message)) { + accept = true; + + stream.addDataListener(listener); + } + } + return accept; + } +} diff --git a/engine/src/networking/com/jme3/network/streaming/ServerStreamingService.java b/engine/src/networking/com/jme3/network/streaming/ServerStreamingService.java new file mode 100644 index 000000000..a4b0756bb --- /dev/null +++ b/engine/src/networking/com/jme3/network/streaming/ServerStreamingService.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.network.streaming; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.Message; +import com.jme3.network.message.StreamDataMessage; +import com.jme3.network.message.StreamMessage; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ServerStreamingService extends MessageAdapter { + private static Logger log = Logger.getLogger(StreamingService.class.getName()); + + protected ArrayList streams; + + private short nextStreamID = Short.MIN_VALUE; + + public ServerStreamingService(Server server) { + streams = new ArrayList(); + server.addMessageListener(this, StreamMessage.class); + } + + public void offerStream(Client client, StreamMessage msg, InputStream data) { + short streamID = ++nextStreamID; + msg.setStreamID(streamID); + msg.setReliable(true); + + Stream stream = new Stream(); + stream.setData(data); + stream.setMessage(msg); + stream.setReceiver(client); + streams.add(stream); + + try { + client.send(msg); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void startStream(Stream stream) { + Client receiver = stream.getReceiver(); + try + { + InputStream data = stream.getData(); + + byte[] buffer = new byte[1024]; + int length; + + StreamDataMessage msg = new StreamDataMessage(stream.getMessage().getStreamID()); + msg.setReliable(true); + + while ((length = data.read(buffer)) != -1) { + byte[] newBuffer = new byte[length]; + + for (int i = 0; i != length; ++i) { + newBuffer[i] = buffer[i]; + } + msg.setData(newBuffer); + receiver.send(msg); + } + data.close(); + + receiver.send(stream.getMessage()); + } catch (Exception ex) { + ex.printStackTrace(); + log.log(Level.WARNING, "[StreamSender][TCP] Could not send stream with message {0} to {1}. Reason: {2}.", new Object[]{stream, receiver, ex.getMessage()}); + } + } + + public void messageReceived(Message message) { + if (message instanceof StreamMessage && !(message instanceof StreamDataMessage)) { + // A stream was accepted. + StreamMessage streamMessage = (StreamMessage)message; + Stream stream = getStream(streamMessage); + + if (stream == null) return; + stream.setAccepted(true); + startStream(stream); + } + } + + private Stream getStream(short id) { + for (Stream stream : streams) { + if (stream.getMessage().getStreamID() == id) return stream; + } + return null; + } + + private Stream getStream(StreamMessage msg) { + for (Stream stream : streams) { + if (stream.getMessage().getStreamID() == msg.getStreamID()) return stream; + } + return null; + } +} diff --git a/engine/src/networking/com/jme3/network/streaming/Stream.java b/engine/src/networking/com/jme3/network/streaming/Stream.java new file mode 100644 index 000000000..cdaf5b2b2 --- /dev/null +++ b/engine/src/networking/com/jme3/network/streaming/Stream.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.network.streaming; + +import com.jme3.network.connection.Client; +import com.jme3.network.message.StreamMessage; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class Stream { + private StreamMessage message; + private InputStream data; + private Client receiver; + + private ArrayList listeners = new ArrayList(); + + private boolean accepted = false; + + public StreamMessage getMessage() { + return message; + } + + public void setMessage(StreamMessage message) { + this.message = message; + } + + public InputStream getData() { + return data; + } + + public void setData(InputStream data) { + this.data = data; + } + + public boolean isAccepted() { + return accepted; + } + + public void setAccepted(boolean accepted) { + this.accepted = accepted; + } + + public Client getReceiver() { + return receiver; + } + + public void setReceiver(Client receiver) { + this.receiver = receiver; + } + + public void addDataListener(StreamListener listener) { + listeners.add(listener); + } + + public void removeDataListener(StreamListener listener) { + listeners.remove(listener); + } + + public List getDataListeners() { + return listeners; + } +} diff --git a/engine/src/networking/com/jme3/network/streaming/StreamListener.java b/engine/src/networking/com/jme3/network/streaming/StreamListener.java new file mode 100644 index 000000000..c4a08e6de --- /dev/null +++ b/engine/src/networking/com/jme3/network/streaming/StreamListener.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.network.streaming; + +import com.jme3.network.message.StreamDataMessage; +import com.jme3.network.message.StreamMessage; + +/** + * Used for stream sending/receiving. + * + * @author Lars Wesselius + */ +public interface StreamListener { + public boolean streamOffered(StreamMessage message); + public void streamDataReceived(StreamDataMessage message); + public void streamCompleted(StreamMessage message); +} diff --git a/engine/src/networking/com/jme3/network/streaming/StreamingService.java b/engine/src/networking/com/jme3/network/streaming/StreamingService.java new file mode 100644 index 000000000..793541ab8 --- /dev/null +++ b/engine/src/networking/com/jme3/network/streaming/StreamingService.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.streaming; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.StreamMessage; +import com.jme3.network.service.Service; + +import java.io.InputStream; + +/** + * Streaming service handles all kinds of streaming to clients. It can be instantiated by + * both the client and server, where server will work as sender, and client as receiver. + * + * @author Lars Wesselius + */ +public class StreamingService extends MessageAdapter implements Service { + + private ClientStreamingService clientService; + private ServerStreamingService serverService; + + public StreamingService(Client client) { + clientService = new ClientStreamingService(client); + } + + public StreamingService(Server server) { + serverService = new ServerStreamingService(server); + } + + public void offerStream(Client client, StreamMessage msg, InputStream data) { + if (serverService == null) return; + serverService.offerStream(client, msg, data); + } + + public void addStreamListener(StreamListener listener) { + if (clientService == null) return; + clientService.addStreamListener(listener); + } + + public void removeStreamListener(StreamListener listener) { + if (clientService == null) return; + clientService.removeStreamListener(listener); + } +} diff --git a/engine/src/networking/com/jme3/network/sync/ClientSyncService.java b/engine/src/networking/com/jme3/network/sync/ClientSyncService.java new file mode 100644 index 000000000..c36015b18 --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/ClientSyncService.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.network.sync; + +import com.jme3.network.connection.Client; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.Message; +import com.jme3.network.service.Service; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.nio.ByteBuffer; + +public class ClientSyncService extends MessageAdapter implements Service { + + private static final ByteBuffer BUFFER = ByteBuffer.wrap(new byte[10000]); + + private static class ClientEntityInfo { + SyncEntity entity; + + EntitySyncInfo lastSyncInfo; + EntitySyncInfo lastCreateInfo; + + long lastUpdate = 0; + long lastExtrapolate = -1; + long lastUpdateRate; + long lastLatencyDelta = Long.MAX_VALUE; + } + +// private final ArrayList syncQueue = +// new ArrayList(); + + private final IntMap entities = + new IntMap(); + + private EntityFactory factory; + private SyncSerializer serializer = new SyncSerializer(); + + private long lastSyncMsgTime = 0; + private int lastHeartbeat; + private MovingAverage averageLatency = new MovingAverage(20); + + public void update(float tpf2){ + long time = System.currentTimeMillis(); + synchronized (entities){ + for (Entry entry : entities){ + ClientEntityInfo info = entry.getValue(); + + if (info.lastSyncInfo != null){ + if (!inLoopApplySyncInfo(entry.getKey(), info)) + continue; // entity was deleted due to this command + } + + long timeSinceUpdate = time - info.lastUpdate; + if (timeSinceUpdate >= info.lastUpdateRate){ + if (info.lastExtrapolate == -1){ + info.entity.interpolate(1); + info.lastExtrapolate = info.lastUpdate + info.lastUpdateRate; + } + + long timeSinceExtrapolate = time - info.lastExtrapolate; + info.lastExtrapolate = time; + float tpf = timeSinceExtrapolate / 1000f; + info.entity.extrapolate(tpf); + }else{ + float blendAmount = (float) timeSinceUpdate / (float)info.lastUpdateRate; + info.entity.interpolate(blendAmount); + } + } + } + } + + public ClientSyncService(Client client){ + client.addMessageListener(this /*, SyncMessage.class*/ ); + } + + public void setEntityFactory(EntityFactory factory){ + this.factory = factory; + } + + public SyncEntity getEntity(int id){ + return entities.get(id).entity; + } + + private void inLoopCreateEntity(int entityId, ClientEntityInfo clientInfo){ + EntitySyncInfo initInfo = clientInfo.lastSyncInfo; + + Class clazz; + try { + clazz = (Class) Class.forName(initInfo.className); + } catch (ClassNotFoundException ex) { + throw new RuntimeException("Cannot find entity class: " + initInfo.className, ex); + } + + SyncEntity entity; + if (factory != null){ + entity = factory.createEntity(clazz); + }else{ + try { + entity = clazz.newInstance(); + } catch (InstantiationException ex) { + throw new RuntimeException("Entity class is missing empty constructor", ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + + clientInfo.entity = entity; + entity.onRemoteCreate(); + + serializer.read(entity, ByteBuffer.wrap(initInfo.data), true); + + clientInfo.lastSyncInfo = null; + } + + private void inLoopSyncEntity(int entityId, ClientEntityInfo entityInfo){ + serializer.read(entityInfo.entity, ByteBuffer.wrap(entityInfo.lastSyncInfo.data), false); + entityInfo.entity.onRemoteUpdate( entityInfo.lastLatencyDelta / 1000f ); + + // clear so its not called again + entityInfo.lastSyncInfo = null; + entityInfo.lastLatencyDelta = Long.MAX_VALUE; + } + + private void inLoopDeleteEntity(int entityId, ClientEntityInfo clientInfo){ + SyncEntity entity = clientInfo.entity; + entity.onRemoteDelete(); + entities.remove(entityId); + } + + private boolean inLoopApplySyncInfo(int entityId, ClientEntityInfo clientInfo){ + switch (clientInfo.lastSyncInfo.type){ + case EntitySyncInfo.TYPE_NEW: + inLoopCreateEntity(entityId, clientInfo); + return true; + case EntitySyncInfo.TYPE_SYNC: + inLoopSyncEntity(entityId, clientInfo); + return true; + case EntitySyncInfo.TYPE_DELETE: + inLoopDeleteEntity(entityId, clientInfo); + return false; + default: + throw new UnsupportedOperationException(); + } + } + + private void createEntity(EntitySyncInfo info){ + ClientEntityInfo entityInfo = new ClientEntityInfo(); + entityInfo.lastUpdate = System.currentTimeMillis(); + + // forces inLoopCreateEntity to be called later + entityInfo.lastSyncInfo = info; + + entities.put(info.id, entityInfo); + } + + private void syncEntity(EntitySyncInfo info, int latencyDelta){ + ClientEntityInfo entityInfo = entities.get(info.id); + if (entityInfo == null || entityInfo.entity == null) + return; // didn't receive init yet. + + long time = System.currentTimeMillis(); + entityInfo.lastUpdateRate = time - entityInfo.lastUpdate; + entityInfo.lastUpdate = time; + entityInfo.lastExtrapolate = -1; + + // forces inLoopSyncEntity to be called later + entityInfo.lastSyncInfo = info; + entityInfo.lastLatencyDelta = latencyDelta; + } + + void deleteEntity(EntitySyncInfo info){ + ClientEntityInfo clientInfo = entities.get(info.id); + clientInfo.lastSyncInfo = info; + } + + private void applySyncInfo(EntitySyncInfo info, int latencyDelta){ + switch (info.type) { + case EntitySyncInfo.TYPE_NEW: + createEntity(info); + break; + case EntitySyncInfo.TYPE_SYNC: + syncEntity(info, latencyDelta); + break; + case EntitySyncInfo.TYPE_DELETE: + deleteEntity(info); + break; + } + } + + @Override + public void messageReceived(Message msg) { + if (!(msg instanceof SyncMessage)) { + return; + } + + int latencyDelta = 0; + if (lastSyncMsgTime == 0) { + // this is the first syncmessage + lastSyncMsgTime = System.currentTimeMillis(); + } else { + long time = System.currentTimeMillis(); + long delta = time - lastSyncMsgTime; + averageLatency.add(delta); + lastSyncMsgTime = time; + latencyDelta = (int) (delta - averageLatency.getAverage()); + } + + SyncMessage sync = (SyncMessage) msg; + + boolean isOldMessage = false; + int newHeartbeat = sync.heartbeat; + if (lastHeartbeat > newHeartbeat){ + // check if at the end of heartbeat indices + // within 1000 heartbeats + if (lastHeartbeat > Integer.MAX_VALUE - 1000 && newHeartbeat < 1000){ + lastHeartbeat = newHeartbeat; + }else{ + isOldMessage = true; + } + }else{ + lastHeartbeat = newHeartbeat; + } + + for (EntitySyncInfo info : sync.infos) { + if (info.type == EntitySyncInfo.TYPE_SYNC && isOldMessage) + continue; // old sync message, ignore. + + applySyncInfo(info, latencyDelta); + } + + } + +} diff --git a/engine/src/networking/com/jme3/network/sync/EntityFactory.java b/engine/src/networking/com/jme3/network/sync/EntityFactory.java new file mode 100644 index 000000000..0e47adc15 --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/EntityFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.sync; + +public interface EntityFactory { + public SyncEntity createEntity(Class entityType); +} diff --git a/engine/src/networking/com/jme3/network/sync/EntitySyncInfo.java b/engine/src/networking/com/jme3/network/sync/EntitySyncInfo.java new file mode 100644 index 000000000..00d44078e --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/EntitySyncInfo.java @@ -0,0 +1,26 @@ +package com.jme3.network.sync; + +public final class EntitySyncInfo { + + public static final byte TYPE_NEW = 0x1, + TYPE_SYNC = 0x2, + TYPE_DELETE = 0x3; + + /** + * NEW, SYNC, or DELETE + */ + public byte type; + /** + * Entity ID + */ + public int id; + /** + * Entity Class Name + */ + public String className; + + /** + * Vars + */ + public byte[] data; +} diff --git a/engine/src/networking/com/jme3/network/sync/MovingAverage.java b/engine/src/networking/com/jme3/network/sync/MovingAverage.java new file mode 100644 index 000000000..cd039d15a --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/MovingAverage.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.network.sync; + +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/networking/com/jme3/network/sync/ServerSyncService.java b/engine/src/networking/com/jme3/network/sync/ServerSyncService.java new file mode 100644 index 000000000..a6f616235 --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/ServerSyncService.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.network.sync; + +import com.jme3.math.FastMath; +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.ConnectionAdapter; +import com.jme3.network.service.Service; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class ServerSyncService extends ConnectionAdapter implements Service { + + private static final ByteBuffer BUFFER = ByteBuffer.wrap(new byte[10000]); + + private float updateRate = 0.1f; + private float packetDropRate = 0; + private long latency = 0; + private HashMap latencyQueue; + + private final Server server; + private final SyncSerializer serializer = new SyncSerializer(); +// private final ArrayList connectedClients = new ArrayList(); + private final ArrayList npcs = new ArrayList(); + private final HashMap npcToId + = new HashMap(); + + private static int nextId = 0; + + private int heartbeat = 0; + private float time = 0; + + public ServerSyncService(Server server){ + this.server = server; + server.addConnectionListener(this); + } + + public void setNetworkSimulationParams(float packetDropRate, long latency){ + if (latencyQueue == null) + latencyQueue = new HashMap(); + + this.packetDropRate = packetDropRate; + this.latency = latency; + } + + private EntitySyncInfo generateInitInfo(SyncEntity entity, boolean newId){ + EntitySyncInfo info = new EntitySyncInfo(); + info.className = entity.getClass().getName(); + info.id = newId ? nextId ++ : npcToId.get(entity); + info.type = EntitySyncInfo.TYPE_NEW; + + BUFFER.clear(); + serializer.write(entity, BUFFER, true); + BUFFER.flip(); + info.data = new byte[BUFFER.limit()]; + BUFFER.get(info.data); + return info; + } + + private EntitySyncInfo generateSyncInfo(SyncEntity entity){ + EntitySyncInfo info = new EntitySyncInfo(); + info.className = null; + info.id = npcToId.get(entity); + info.type = EntitySyncInfo.TYPE_SYNC; + + BUFFER.clear(); + serializer.write(entity, BUFFER, false); + BUFFER.flip(); + info.data = new byte[BUFFER.limit()]; + BUFFER.get(info.data); + return info; + } + + private EntitySyncInfo generateDeleteInfo(SyncEntity entity){ + EntitySyncInfo info = new EntitySyncInfo(); + info.className = null; + info.id = npcToId.get(entity); + info.type = EntitySyncInfo.TYPE_DELETE; + return info; + } + + public void addNpc(SyncEntity entity){ + EntitySyncInfo info = generateInitInfo(entity, true); + SyncMessage syncMsg = new SyncMessage(); + syncMsg.setReliable(true); + syncMsg.heartbeat = heartbeat; + syncMsg.infos = new EntitySyncInfo[]{ info }; + + try { + server.broadcast(syncMsg); + } catch (IOException ex) { + ex.printStackTrace(); + } + + synchronized (npcs){ + npcs.add(entity); + npcToId.put(entity, info.id); + } + } + + public void removeNpc(SyncEntity entity){ + EntitySyncInfo info = generateDeleteInfo(entity); + + SyncMessage syncMsg = new SyncMessage(); + syncMsg.setReliable(true); + syncMsg.heartbeat = heartbeat; + syncMsg.infos = new EntitySyncInfo[]{ info }; + + try { + server.broadcast(syncMsg); + } catch (IOException ex) { + ex.printStackTrace(); + } + + synchronized (npcs){ + npcs.remove(entity); + npcToId.remove(entity); + } + } + + @Override + public void clientConnected(Client id){ + System.out.println("Server: Client connected: " + id); + SyncMessage msg = new SyncMessage(); + msg.setReliable(true); // sending INIT information, has to be reliable. + + msg.heartbeat = heartbeat; + EntitySyncInfo[] infos = new EntitySyncInfo[npcs.size()]; + msg.infos = infos; + synchronized (npcs){ + for (int i = 0; i < npcs.size(); i++){ + SyncEntity entity = npcs.get(i); + EntitySyncInfo info = generateInitInfo(entity, false); + infos[i] = info; + } + + try { + id.send(msg); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + @Override + public void clientDisconnected(Client id){ + System.out.println("Server: Client disconnected: " + id); + } + + private void sendDelayedMessages(){ + ArrayList removeList = new ArrayList(); + for (Map.Entry entry : latencyQueue.entrySet()){ + if (entry.getKey() > System.currentTimeMillis()) + continue; + + removeList.add(entry.getKey()); + if (packetDropRate > FastMath.nextRandomFloat()) + continue; + + for (Client id : server.getConnectors()){ + try { + id.send(entry.getValue()); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + for (Long removeEntry : removeList) + latencyQueue.remove(removeEntry); + } + + public void update(float tpf){ + if (latencyQueue != null) + sendDelayedMessages(); + + if (npcs.size() == 0) + return; + + time += tpf; + if (time < updateRate){ + return; + }else{ + time = 0; + } + + SyncMessage msg = new SyncMessage(); + msg.setReliable(false); // Purely SYNC message, reliability not needed + + msg.heartbeat = heartbeat; + synchronized (npcs){ + EntitySyncInfo[] infos = new EntitySyncInfo[npcs.size()]; + msg.infos = infos; + for (int i = 0; i < npcs.size(); i++){ + SyncEntity entity = npcs.get(i); + EntitySyncInfo info = generateSyncInfo(entity); + entity.onLocalUpdate(); + infos[i] = info; + } + } + + if (latencyQueue != null){ + long latencyTime = (long) (latency + (FastMath.nextRandomFloat()-0.5f) * latency); + long timeToSend = System.currentTimeMillis() + latencyTime; + latencyQueue.put(timeToSend, msg); + }else{ + for (Client id : server.getConnectors()){ + try { + id.send(msg); // unreliable + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + heartbeat++; + if (heartbeat < 0){ + // overflow detected + heartbeat = 0; + } + } + +} diff --git a/engine/src/networking/com/jme3/network/sync/Sync.java b/engine/src/networking/com/jme3/network/sync/Sync.java new file mode 100644 index 000000000..c394c5a86 --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/Sync.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.network.sync; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Sync { + + public enum SyncType { InitOnly, Init, Sync } + + boolean smooth() default false; + SyncType value() default SyncType.Sync; +} diff --git a/engine/src/networking/com/jme3/network/sync/SyncEntity.java b/engine/src/networking/com/jme3/network/sync/SyncEntity.java new file mode 100644 index 000000000..4cf39751e --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/SyncEntity.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.network.sync; + +public interface SyncEntity { + + public void onRemoteCreate(); + public void onRemoteUpdate(float latencyDelta); + public void onRemoteDelete(); + public void onLocalUpdate(); + + public void interpolate(float blendAmount); + public void extrapolate(float tpf); + +} diff --git a/engine/src/networking/com/jme3/network/sync/SyncMessage.java b/engine/src/networking/com/jme3/network/sync/SyncMessage.java new file mode 100644 index 000000000..1b5b4c402 --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/SyncMessage.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.network.sync; + +import com.jme3.network.message.Message; +import com.jme3.network.serializing.Serializable; + +@Serializable +public class SyncMessage extends Message { + public int heartbeat; + public EntitySyncInfo[] infos; +} diff --git a/engine/src/networking/com/jme3/network/sync/SyncSerializer.java b/engine/src/networking/com/jme3/network/sync/SyncSerializer.java new file mode 100644 index 000000000..da461d57b --- /dev/null +++ b/engine/src/networking/com/jme3/network/sync/SyncSerializer.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.network.sync; + +import com.jme3.network.serializing.Serializer; +import com.jme3.network.sync.Sync.SyncType; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; + +class SyncSerializer { + + static class SyncFieldInfo { + private Field field; + private boolean init,sync,smooth; + } + + static class FieldTable extends ArrayList { + } + + private HashMap, FieldTable> classFieldTables + = new HashMap, FieldTable>(); + + private FieldTable generateFieldTable(Class clazz){ + FieldTable table = new FieldTable(); + while (clazz != null){ + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields){ + Sync syncAnnot = field.getAnnotation(Sync.class); + if (syncAnnot == null) + continue; + + SyncFieldInfo info = new SyncFieldInfo(); + field.setAccessible(true); + info.field = field; + info.init = syncAnnot.value() == SyncType.Init + || syncAnnot.value() == SyncType.InitOnly; + info.sync = syncAnnot.value() != SyncType.InitOnly; + info.smooth = syncAnnot.smooth(); + table.add(info); + } + + clazz = clazz.getSuperclass(); + } + + return table; + } + + private FieldTable getTable(Class clazz){ + FieldTable table = classFieldTables.get(clazz); + if (table == null){ + table = generateFieldTable(clazz); + classFieldTables.put(clazz, table); + } + return table; + } + + public void read(Object entity, ByteBuffer in, boolean init){ + FieldTable table = getTable(entity.getClass()); + for (SyncFieldInfo fieldInfo : table){ + if ( (init && !fieldInfo.init) + || (!init && !fieldInfo.sync) ) + continue; + + Field field = fieldInfo.field; + try { + Object obj = Serializer.readClassAndObject(in); + field.set(entity, obj); + } catch (Exception ex){ + ex.printStackTrace(); + } + } + } + + public void write(Object entity, ByteBuffer out, boolean init){ + FieldTable table = getTable(entity.getClass()); + for (SyncFieldInfo fieldInfo : table){ + if ( (init && !fieldInfo.init) + || (!init && !fieldInfo.sync) ) + continue; + + Field field = fieldInfo.field; + try { + Serializer.writeClassAndObject(out, field.get(entity)); + } catch (Exception ex){ + ex.printStackTrace(); + } + } + } +} 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 000000000..5e77548d1 --- /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 000000000..14bd7ef3f --- /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 000000000..67c864d96 --- /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/niftygui/InputSystemJme.java b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java new file mode 100644 index 000000000..897f5431b --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputEvent; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.NiftyInputConsumer; +import de.lessvoid.nifty.input.keyboard.KeyboardInputEvent; +import de.lessvoid.nifty.input.mouse.MouseInputEvent; +import de.lessvoid.nifty.spi.input.InputSystem; +import java.util.ArrayList; + +public class InputSystemJme implements InputSystem, RawInputListener { + + private final ArrayList inputQueue = new ArrayList(); + + private InputManager inputManager; + + private boolean pressed = false; + 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 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(){ + //requires nifty1.3 + boolean result = nifty.update(); + } + + public void onJoyAxisEvent(JoyAxisEvent evt) { + } + + public void onJoyButtonEvent(JoyButtonEvent evt) { + } + + private void onMouseMotionEventQueued(MouseMotionEvent evt, NiftyInputConsumer nic) { + x = evt.getX(); + y = height - evt.getY(); + MouseInputEvent niftyEvt = new MouseInputEvent(x, y, pressed); + if (nic.processMouseEvent(niftyEvt) /*|| nifty.getCurrentScreen().isMouseOverElement()*/){ + // Do not consume motion events + //evt.setConsumed(); + } + } + + public void onMouseMotionEvent(MouseMotionEvent evt) { + // Only forward the event if there's actual motion involved. + // Ignores mouse wheel + if (inputManager.isCursorVisible() && (evt.getDX() != 0 || + evt.getDY() != 0)){ + inputQueue.add(evt); + } + } + + private void onMouseButtonEventQueued(MouseButtonEvent evt, NiftyInputConsumer nic) { + pressed = evt.isPressed(); + MouseInputEvent niftyEvt = new MouseInputEvent(x, y, pressed); + if (nic.processMouseEvent(niftyEvt) /*|| nifty.getCurrentScreen().isMouseOverElement()*/){ + evt.setConsumed(); + } + } + + public void onMouseButtonEvent(MouseButtonEvent evt) { + if (inputManager.isCursorVisible() && evt.getButtonIndex() == 0){ + inputQueue.add(evt); + } + } + + 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 onKeyEvent(KeyInputEvent 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); + } + } + + 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 000000000..4f30be8be --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.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.niftygui; + +import com.jme3.asset.AssetManager; +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; + +public class NiftyJmeDisplay extends TimeProvider 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 int w, h; + + 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, this); + inputSys.setNifty(nifty); + } + + 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); + } + + 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 000000000..27e6ab8bd --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/RenderDeviceJme.java @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.*; +import de.lessvoid.nifty.tools.Color; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class RenderDeviceJme implements RenderDevice { + + private NiftyJmeDisplay display; + private RenderManager rm; + private Renderer r; + + 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 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() { + } + + 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); + } + + public void renderFont(RenderFont font, String str, int x, int y, Color color, float size){ + if (str.length() == 0) + return; + + if (font instanceof RenderFontNull) + return; + + RenderFontJme jmeFont = (RenderFontJme) font; + BitmapText text = jmeFont.getText(); + + // WARNING: Not compatible with OpenGL1 implementations.. + niftyMat.setColor("Color", convertColor(color, tempColor)); + + niftyMat.setBoolean("UseTex", true); + niftyMat.getAdditionalRenderState().setBlendMode(convertBlend()); + text.setMaterial(niftyMat); + + text.setText(str); + text.updateLogicalState(0); + + float width = text.getLineWidth(); + float height = text.getLineHeight(); + + float x0 = x + 0.5f * width * (1f - size); + float y0 = y + 0.5f * height * (1f - size); + + tempMat.loadIdentity(); + tempMat.setTranslation(x0, getHeight() - y0, 0); + tempMat.setScale(size, size, 0); + + rm.setWorldMatrix(tempMat); + text.render(rm); + } + + 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 = texture.getImage().getWidth(); + float imageHeight = texture.getImage().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); + } + + 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); + } + + 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); + } + + 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); + } + + public void enableClip(int x0, int y0, int x1, int y1){ + clipWasSet = true; + r.setClipRect(x0, getHeight() - y1, x1 - x0, y1 - y0); + } + + public void 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 000000000..0bfef504c --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/RenderFontJme.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.niftygui; + +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +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); + text = new BitmapText(font); + actualSize = font.getPreferredSize(); + text.setSize(actualSize); + } + + 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 text text + * @return width of the given text for the current font + */ + public int getWidth(final String str) { + if (str.length() == 0) + return 0; + + int result = (int) font.getLineWidth(str); +// text.setText(str); +// text.updateLogicalState(0); +// int result = (int) text.getLineWidth(); + + return result; + } + + /** + * 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 Integer getCharacterAdvance(final char currentCharacter, final char nextCharacter, final float size) { + return Integer.valueOf( Math.round(font.getCharacterAdvance(currentCharacter, nextCharacter, size)) ); + } + + public void dispose() { + } +} \ No newline at end of file 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 000000000..fd837b8ed --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/RenderImageJme.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.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; + + 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(MagFilter.Bilinear); + texture.setMinFilter(MinFilter.BilinearNoMipMaps); + image = texture.getImage(); + } + + public RenderImageJme(Texture2D texture){ + this.texture = texture; + this.image = texture.getImage(); + if (this.image == null) + throw new NullPointerException("texture.getImage() cannot be null"); + } + + public Texture2D getTexture(){ + return texture; + } + + public int getWidth() { + return image.getWidth(); + } + + public int getHeight() { + return image.getHeight(); + } + + 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 000000000..8471eb41a --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/SoundDeviceJme.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.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; + +public class SoundDeviceJme implements SoundDevice { + + protected AssetManager assetManager; + protected AudioRenderer ar; + + public SoundDeviceJme(AssetManager assetManager, AudioRenderer ar){ + this.assetManager = assetManager; + this.ar = ar; + } + + 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 000000000..c39b18f5b --- /dev/null +++ b/engine/src/niftygui/com/jme3/niftygui/SoundHandleJme.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.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 AudioRenderer ar; + 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.ar = ar; + 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.ar = ar; + this.am = am; + if (fileName == null) + throw new NullPointerException(); + + this.fileName = fileName; + } + + public void play() { + if (fileName != null){ + if (node != null){ + ar.stopSource(node); + } + + node = new AudioNode(am, fileName, true); + node.setPositional(false); + node.setVolume(volume); + ar.playSource(node); + }else{ + ar.playSourceInstance(node); + } + } + + public void stop() { + if (fileName != null){ + ar.stopSource(node); + 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 000000000..9abd09959 --- /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.BoneAnimation; +import com.jme3.animation.Skeleton; +import java.util.ArrayList; + +public class AnimData { + + public final Skeleton skeleton; + public final ArrayList anims; + + public AnimData(Skeleton skeleton, ArrayList 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 000000000..11e7c98b2 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/MaterialLoader.java @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +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.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Locale; +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 Scanner scan; + private ColorRGBA ambient, diffuse, specular; + private Texture texture; + 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 String readString(String end){ + scan.useDelimiter(end); + String str = scan.next(); + scan.useDelimiter("\\p{javaWhitespace}+"); + return str.trim(); + } + + private ColorRGBA readColor(){ + ColorRGBA color = new ColorRGBA(); + color.r = scan.nextFloat(); + color.g = scan.nextFloat(); + color.b = scan.nextFloat(); + if (scan.hasNextFloat()){ + color.a = scan.nextFloat(); + } + return color; + } + + private void readTextureImage(){ + // texture image def + String ln = scan.nextLine(); + String path = null; + + // find extension + int extStart = ln.lastIndexOf("."); + for (int i = extStart; i < ln.length(); i++){ + char c = ln.charAt(i); + if (Character.isWhitespace(c)){ + // extension ends here + path = ln.substring(0, i).trim(); + ln = ln.substring(i+1).trim(); + break; + } + } + if (path == null){ + path = ln.trim(); + ln = ""; + } + + Scanner lnScan = new Scanner(ln); + 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 key = new TextureKey(folderName + path, false); + key.setGenerateMips(genMips); + key.setAsCube(cubic); + + Texture loadedTexture = assetManager.loadTexture(key); + if (loadedTexture == null){ + ByteBuffer tempData = BufferUtils.createByteBuffer(3); + tempData.put((byte)0xFF).put((byte)0x00).put((byte)0x00); + texture = new Texture2D(new Image(Format.RGB8, 1,1,tempData)); + logger.log(Level.WARNING, "Using RED texture instead of {0}", path); + }else{ + texture.setImage(loadedTexture.getImage()); + texture.setMinFilter(loadedTexture.getMinFilter()); + texture.setKey(loadedTexture.getKey()); + + // XXX: Is this really neccessary? + texture.setWrap(WrapMode.Repeat); + if (texName != null){ + texture.setName(texName); + texName = null; + }else{ + texture.setName(key.getName()); + } + } + + + } + + private void readTextureUnitStatement(){ + String keyword = scan.next(); + if (keyword.equals("texture")){ + readTextureImage(); + }else if (keyword.equals("tex_address_mode")){ + String mode = scan.next(); + if (mode.equals("wrap")){ + texture.setWrap(WrapMode.Repeat); + }else if (mode.equals("clamp")){ + texture.setWrap(WrapMode.Clamp); + }else if (mode.equals("mirror")){ + texture.setWrap(WrapMode.MirroredRepeat); + }else if (mode.equals("border")){ + texture.setWrap(WrapMode.BorderClamp); + } + }else if (keyword.equals("filtering")){ + // ignored.. only anisotropy is considered + readString("\n"); + }else if (keyword.equals("max_anisotropy")){ + int amount = scan.nextInt(); + texture.setAnisotropicFilter(amount); + }else{ + logger.log(Level.WARNING, "Unsupported texture_unit directive: {0}", keyword); + readString("\n"); + } + } + + private void readTextureUnit(){ + // name is optional + if (!scan.hasNext("\\{")){ + texName = readString("\\{"); + }else{ + texName = null; + } + scan.next(); // skip "{" + + texture = new Texture2D(); + + while (!scan.hasNext("\\}")){ + readTextureUnitStatement(); + } + scan.next(); // skip "}" + } + + private void readPassStatement(){ + // read until newline + String keyword = scan.next(); + if (keyword.equals("")) + return; + + if (keyword.equals("diffuse")){ + if (scan.hasNext("vertexcolour")){ + // use vertex colors + diffuse = ColorRGBA.White; + vcolor = true; + scan.next(); // skip it + }else{ + diffuse = readColor(); + } + }else if(keyword.equals("ambient")) { + ambient = readColor(); + }else if (keyword.equals("specular")){ + specular = new ColorRGBA(); + specular.r = scan.nextFloat(); + specular.g = scan.nextFloat(); + specular.b = scan.nextFloat(); + float unknown = scan.nextFloat(); + if (scan.hasNextFloat()){ + // using 5 float values + specular.a = unknown; + shinines = scan.nextFloat(); + }else{ + // using 4 float values + specular.a = 1f; + shinines = unknown; + } + }else if (keyword.equals("texture_unit")){ + readTextureUnit(); + }else if (keyword.equals("scene_blend")){ + String mode = scan.next(); + if (mode.equals("alpha_blend")){ + blend = true; + } + }else if (keyword.equals("cull_hardware")){ + String mode = scan.next(); + if (mode.equals("none")){ + twoSide = true; + } + }else if (keyword.equals("cull_software")){ + // ignore + scan.next(); + }else if (keyword.equals("lighting")){ + String isOn = scan.next(); + if (isOn.equals("on")){ + noLight = false; + }else if (isOn.equals("off")){ + noLight = true; + } + }else{ + logger.log(Level.WARNING, "Unsupported pass directive: {0}", keyword); + readString("\n"); + } + } + + private void readPass(){ + scan.next(); // skip "pass" + // name is optional + String name; + if (scan.hasNext("\\{")){ + // no name + name = null; + }else{ + name = readString("\\{"); + } + scan.next(); // skip "{" + while (!scan.hasNext("\\}")){ + readPassStatement(); + } + scan.next(); // skip "}" + } + + private void readTechnique(){ + scan.next(); // skip "technique" + // name is optional + String name; + if (scan.hasNext("\\{")){ + // no name + name = null; + }else{ + name = readString("\\{"); + } + scan.next(); // skip "{" + while (!scan.hasNext("\\}")){ + readPass(); + } + scan.next(); + } + + private boolean readMaterialStatement(){ + if (scan.hasNext("technique")){ + readTechnique(); + return true; + }else if (scan.hasNext("receive_shadows")){ + // skip "receive_shadows" + scan.next(); + String isOn = scan.next(); + if (isOn != null && isOn.equals("true")){ + + } + return true; + }else{ + return false; + } + } + + @SuppressWarnings("empty-statement") + private void readMaterial(){ + scan.next(); // skip "material" + // read name + matName = readString("\\{"); + scan.next(); // skip "{" + while (!scan.hasNext("\\}")){ + readMaterialStatement(); + } + scan.next(); + } + + 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 (texture != null) + mat.setTexture("DiffuseMap", texture); + + 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); + } + }else{ + if (vcolor) + mat.setBoolean("VertexColor", true); + + if (texture != null) + mat.setTexture("ColorMap", texture); + + if(diffuse != null){ + mat.setColor("Color", diffuse); + } + } + + noLight = false; + texture = null; + diffuse = null; + specular = null; + texture = null; + shinines = 0f; + vcolor = false; + blend = false; + return mat; + } + + public Object load(AssetInfo info) throws IOException { + folderName = info.getKey().getFolder(); + assetManager = info.getManager(); + + MaterialList list; + + scan = new Scanner(info.openStream()); + scan.useLocale(Locale.US); + if (scan.hasNext("import")){ + MaterialExtensionSet matExts = null; + if (info.getKey() instanceof OgreMaterialKey){ + matExts = ((OgreMaterialKey)info.getKey()).getMaterialExtensionSet(); + } + + if (matExts == null){ + throw new IOException("Must specify MaterialExtensionSet when loading\n"+ + "Ogre3D materials with extended materials"); + } + + list = new MaterialExtensionLoader().load(assetManager, matExts, scan); + }else{ + list = new MaterialList(); + while (scan.hasNext("material")){ + readMaterial(); + Material mat = compileMaterial(); + list.put(matName, mat); + } + } + scan.close(); + return list; + } + +} 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 000000000..544206af4 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java @@ -0,0 +1,824 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.BoneAnimation; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +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.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; +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 com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +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; +import org.xml.sax.helpers.XMLReaderFactory; + +import static com.jme3.util.xml.SAXUtil.*; + +/** + * 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 String meshName; + private String folderName; + private AssetManager assetManager; + private MaterialList materialList; + + private ShortBuffer sb; + private IntBuffer ib; + private FloatBuffer fb; + private VertexBuffer vb; + private Mesh mesh; + private Geometry geom; + private Mesh sharedmesh; + private Geometry sharedgeom; + private int geomIdx = 0; + private int texCoordIdx = 0; + private static volatile int nodeIdx = 0; + private String ignoreUntilEnd = null; + private boolean bigindices = false; + private int vertCount; + private int triCount; + + private List geoms = new ArrayList(); + private List usesSharedGeom = new ArrayList(); + private IntMap> lodLevels = new IntMap>(); + private AnimData animData; + + private ByteBuffer indicesData; + private FloatBuffer weightsFloatData; + + public MeshLoader(){ + super(); + } + + @Override + public void startDocument() { + geoms.clear(); + usesSharedGeom.clear(); + lodLevels.clear(); + + sb = null; + ib = null; + fb = null; + vb = null; + mesh = null; + geom = null; + sharedgeom = null; + sharedmesh = null; + + vertCount = 0; + triCount = 0; + geomIdx = 0; + texCoordIdx = 0; + nodeIdx = 0; + ignoreUntilEnd = null; + + animData = null; + + indicesData = null; + weightsFloatData = null; + } + + @Override + public void endDocument() { + } + + private void pushFace(String v1, String v2, String v3) throws SAXException{ + int i1 = parseInt(v1); + + // TODO: fan/strip support + int i2 = parseInt(v2); + int i3 = parseInt(v3); + if (ib != null){ + ib.put(i1).put(i2).put(i3); + }else{ + sb.put((short)i1).put((short)i2).put((short)i3); + } + } + + private void startFaces(String count) throws SAXException{ + int numFaces = parseInt(count); + int numIndices; + + if (mesh.getMode() == Mesh.Mode.Triangles){ + //mesh.setTriangleCount(numFaces); + numIndices = numFaces * 3; + }else{ + throw new SAXException("Triangle strip or fan not supported!"); + } + + int numVerts; + if (usesSharedGeom.size() > 0 && usesSharedGeom.get(geoms.size()-1)){ +// sharedgeom.getMesh().updateCounts(); + numVerts = sharedmesh.getVertexCount(); + }else{ +// mesh.updateCounts(); + numVerts = mesh.getVertexCount(); + } + vb = new VertexBuffer(VertexBuffer.Type.Index); + if (!bigindices){ + sb = BufferUtils.createShortBuffer(numIndices); + ib = null; + vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb); + }else{ + ib = BufferUtils.createIntBuffer(numIndices); + sb = null; + vb.setupData(Usage.Static, 3, 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 + mat = assetManager.loadMaterial(matName); + }else{ + if (materialList != null){ + mat = materialList.get(matName); + } + if (mat == null){ + logger.log(Level.WARNING, "Material {0} not found. Applying default material", matName); + mat = (Material) assetManager.loadAsset(new AssetKey("Common/Materials/RedColor.j3m")); + } + } + + if (mat == null) + throw new RuntimeException("Cannot locate material named " + matName); + + if (mat.isTransparent()) + geom.setQueueBucket(Bucket.Transparent); +// else +// geom.setShadowMode(ShadowMode.CastAndReceive); + +// if (mat.isReceivesShadows()) + + + geom.setMaterial(mat); + } + + private void startMesh(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); + } + + bigindices = parseBool(use32bitIndices, false); + boolean sharedverts = parseBool(usesharedvertices, false); + if (sharedverts){ + usesSharedGeom.add(true); + // import vertexbuffers from shared geom + IntMap sharedBufs = sharedmesh.getBuffers(); + for (Entry entry : sharedBufs){ + mesh.setBuffer(entry.getValue()); + } + // this mesh is shared! + }else{ + usesSharedGeom.add(false); + } + + if (meshName == null) + geom = new Geometry("OgreSubmesh-"+(++geomIdx), mesh); + else + geom = new Geometry(meshName+"-geom-"+(++geomIdx), mesh); + + applyMaterial(geom, matName); + geoms.add(geom); + } + + private void startSharedGeom(String vertexcount) throws SAXException{ + sharedmesh = new Mesh(); + vertCount = parseInt(vertexcount); +// sharedmesh.setVertexCount(vertCount); + + if (meshName == null) + sharedgeom = new Geometry("Ogre-SharedGeom", sharedmesh); + else + sharedgeom = new Geometry(meshName+"-sharedgeom", sharedmesh); + + sharedgeom.setCullHint(CullHint.Always); + geoms.add(sharedgeom); + usesSharedGeom.add(false); // shared geometry doesnt use shared geometry (?) + + geom = sharedgeom; + mesh = sharedmesh; + } + + private void startGeometry(String vertexcount) throws SAXException{ + vertCount = parseInt(vertexcount); +// mesh.setVertexCount(vertCount); + } + + /** + * Normalizes weights if needed and finds largest amount of weights used + * for all vertices in the buffer. + */ + private void endBoneAssigns(){ + if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)){ + 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(); + + weightsFloatData = null; + indicesData = null; + + mesh.setMaxNumWeights(maxWeightsPerVert); + } + + private void startBoneAssigns(){ + if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)){ + // 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 >= 2) + throw new SAXException("More than 2 texture coordinates not supported"); + + if (i == 0){ + vb = new VertexBuffer(Type.TexCoord); + }else{ + vb = new VertexBuffer(Type.TexCoord2); + } + fb = BufferUtils.createFloatBuffer(vertCount * dims); + vb.setupData(Usage.Static, dims, Format.Float, fb); + mesh.setBuffer(vb); + } + } + + private void startVertex(){ + texCoordIdx = 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 (texCoordIdx >= 1) + return; // TODO: Support multi-texcoords + + VertexBuffer tcvb = mesh.getBuffer(Type.TexCoord); + 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"))); + } + } + } + + texCoordIdx++; + } + + 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(" "); + 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); + int faceCount = Integer.parseInt(numfaces); + + vb = new VertexBuffer(VertexBuffer.Type.Index); + sb = BufferUtils.createShortBuffer(faceCount * 3); + ib = null; + vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb); + + List levels = lodLevels.get(index); + if (levels == null){ + levels = new ArrayList(); + Mesh submesh = geoms.get(index).getMesh(); + levels.add(submesh.getBuffer(Type.Index)); + 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> entry : lodLevels){ + Mesh m = geoms.get(entry.getKey()).getMesh(); + List levels = entry.getValue(); + VertexBuffer[] levelArray = new VertexBuffer[levels.size()]; + levels.toArray(levelArray); + m.setLodLevels(levelArray); + } + } + + private void startLodGenerated(String depthsqr){ +// dist = Float.parseFloat(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; + // see which weights are unused for a given bone + for (i = vert * 4; i < vert * 4 + 4; i++){ + float v = weightsFloatData.get(i); + if (v == 0) + break; + } + + weightsFloatData.put(i, w); + indicesData.put(i, bone); + } + + private void startSkeleton(String name){ + animData = (AnimData) assetManager.loadAsset(folderName + name + ".xml"); + //TODO:workaround for meshxml / mesh.xml + if(animData==null) + animData = (AnimData) assetManager.loadAsset(folderName + name + "xml"); + } + + 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")){ + startMesh(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("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")){ + bigindices = false; + geom = null; + mesh = null; + }else if (qName.equals("submeshes")){ + // IMPORTANT: restore sharedgeoemtry, for use with shared boneweights + geom = sharedgeom; + mesh = sharedmesh; + }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 bufs = mesh.getBuffers(); + for (Entry 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 void createBindPose(Mesh mesh){ + VertexBuffer pos = mesh.getBuffer(Type.Position); + if (pos == null || mesh.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())); + mesh.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 = mesh.getBuffer(Type.Normal); + if (norm != null){ + VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal); + bindNorm.setupData(Usage.CpuOnly, + 3, + Format.Float, + BufferUtils.clone(norm.getData())); + mesh.setBuffer(bindNorm); + norm.setUsage(Usage.Stream); + } + } + + private Node compileModel(){ + String nodeName; + if (meshName == null) + nodeName = "OgreMesh"+(++nodeIdx); + else + nodeName = meshName+"-ogremesh"; + + Node model = new Node(nodeName); + if (animData != null){ + ArrayList newMeshes = new ArrayList(geoms.size()); + + // generate bind pose for mesh and add to skin-list + // ONLY if not using shared geometry + // This includes the shared geoemtry itself actually + for (int i = 0; i < geoms.size(); i++){ + Geometry g = geoms.get(i); + Mesh m = geoms.get(i).getMesh(); + boolean useShared = usesSharedGeom.get(i); + // create bind pose + if (!useShared){ + createBindPose(m); + newMeshes.add(m); + }else{ + VertexBuffer bindPos = sharedmesh.getBuffer(Type.BindPosePosition); + VertexBuffer bindNorm = sharedmesh.getBuffer(Type.BindPoseNormal); + VertexBuffer boneIndex = sharedmesh.getBuffer(Type.BoneIndex); + VertexBuffer boneWeight = sharedmesh.getBuffer(Type.BoneWeight); + + if (bindPos != null) + m.setBuffer(bindPos); + + if (bindNorm != null) + m.setBuffer(bindNorm); + + if (boneIndex != null) + m.setBuffer(boneIndex); + + if (boneWeight != null) + m.setBuffer(boneWeight); + } + } + Mesh[] meshes = new Mesh[newMeshes.size()]; + for (int i = 0; i < meshes.length; i++) + meshes[i] = newMeshes.get(i); + + HashMap anims = new HashMap(); + ArrayList animList = animData.anims; + for (int i = 0; i < animList.size(); i++){ + BoneAnimation anim = animList.get(i); + anims.put(anim.getName(), anim); + } + + AnimControl ctrl = new AnimControl(model, + meshes, + animData.skeleton); + ctrl.setAnimations(anims); + model.addControl(ctrl); + } + + for (int i = 0; i < geoms.size(); i++){ + Geometry g = geoms.get(i); + Mesh m = g.getMesh(); + if (sharedmesh != null && usesSharedGeom.get(i)){ + m.setBound(sharedmesh.getBound().clone()); + } + model.attachChild(geoms.get(i)); + } + + return model; + } + + public Object load(AssetInfo info) throws IOException { + try{ + AssetKey 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(); + + OgreMeshKey meshKey = null; + if (key instanceof OgreMeshKey){ + meshKey = (OgreMeshKey) key; + materialList = meshKey.getMaterialList(); + }else{ + materialList = (MaterialList) assetManager.loadAsset(folderName + meshName + ".material"); + } + + XMLReader xr = XMLReaderFactory.createXMLReader(); + xr.setContentHandler(this); + xr.setErrorHandler(this); + InputStreamReader r = new InputStreamReader(info.openStream()); + xr.parse(new InputSource(r)); + r.close(); + + return compileModel(); + }catch (SAXException 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 000000000..fc3324323 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/OgreMeshKey.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.scene.plugins.ogre; + +import com.jme3.asset.ModelKey; +import com.jme3.material.MaterialList; + +public class OgreMeshKey extends ModelKey { + + private MaterialList materialList; + + public OgreMeshKey(String name, MaterialList materialList){ + super(name); + this.materialList = materialList; + } + + public OgreMeshKey(){ + super(); + } + + public MaterialList getMaterialList() { + return materialList; + } + +} 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 000000000..119c26d4b --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/SceneLoader.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.material.MaterialList; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import 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 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; + +import org.xml.sax.helpers.XMLReaderFactory; +import static com.jme3.util.xml.SAXUtil.*; + +public class SceneLoader extends DefaultHandler implements AssetLoader { + + private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + + private Stack elementStack = new Stack(); + private String sceneName; + private String folderName; + private AssetManager assetManager; + private MaterialList materialList; + private Node root; + private Node node; + private 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 Quaternion parseQuat(Attributes attribs) throws SAXException{ + if (attribs.getValue("x") != null){ + // defined as quaternion + // qx, qy, qz, qw defined + 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){ + 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{ + 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 { + assert elementStack.peek().equals("light"); + + // SpotLight will be supporting a direction-normal, too. + if (light instanceof DirectionalLight) + ((DirectionalLight) light).setDirection(parseVector3(attribs)); + } + + private void parseLightAttenuation(Attributes attribs) throws SAXException { + // NOTE: Only radius is supported atm ( for pointlights only, since there are no spotlights, yet). + assert elementStack.peek().equals("light"); + + // SpotLight will be supporting a direction-normal, too. + if (light instanceof PointLight){ + 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; + } + ((PointLight) light).setRadius(range); + } + + } + + private void parseLight(Attributes attribs) throws SAXException { + assert node != null; + assert node.getParent() != null; + assert elementStack.peek().equals("node"); + + String lightType = parseString(attribs.getValue("type"), "point"); + if(lightType.equals("point")) { + light = new PointLight(); + } else if(lightType.equals("directional")) { + light = new DirectionalLight(); + } else if(lightType.equals("spotLight")) { + // TODO: SpotLight class. + logger.warning("No SpotLight class atm, using Pointlight instead."); + light = new PointLight(); + } 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")){ + assert elementStack.size() == 0; + String version = attribs.getValue("formatVersion"); + if (version == null || !version.equals("1.0.0")) + logger.log(Level.WARNING, "Unrecognized version number" + + " in dotScene file: {0}", version); + + }else if (qName.equals("nodes")){ + assert root == null; + if (sceneName == null) + root = new Node("OgreDotScene"+(++sceneIdx)); + else + root = new Node(sceneName+"-scene_node"); + + node = root; + }else if (qName.equals("externals")){ + assert elementStack.peek().equals("scene"); + + }else if (qName.equals("item")){ + assert elementStack.peek().equals("externals"); + }else if (qName.equals("file")){ + assert elementStack.peek().equals("item"); + materialList = (MaterialList) + assetManager.loadAsset(folderName+attribs.getValue("name")); + + }else if (qName.equals("node")){ + String curElement = elementStack.peek(); + assert curElement.equals("nodes") || curElement.equals("node"); + String name = attribs.getValue("name"); + if (name == null) + name = "OgreNode-" + (++nodeIdx); + + Node newNode = new 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")){ + assert elementStack.peek().equals("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"); + + String materialName = attribs.getValue("materialName"); + + // NOTE: append "xml" since its assumed mesh filse are binary in dotScene + if (folderName != null) + meshFile = folderName + meshFile; + + meshFile += ".xml"; + + entityNode = new Node(name); + OgreMeshKey key = new OgreMeshKey(meshFile, materialList); + Spatial ogreMesh = + (Spatial) assetManager.loadAsset(key); + //TODO:workaround for meshxml / mesh.xml + if(ogreMesh==null){ + meshFile = folderName + attribs.getValue("meshFile") + "xml"; + key = new OgreMeshKey(meshFile, materialList); + ogreMesh = (Spatial) assetManager.loadAsset(key); + } + entityNode.attachChild(ogreMesh); + node.attachChild(entityNode); + node = null; + }else if (qName.equals("position")){ + 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")) { + assert elementStack.peek().equals("light"); + light.setColor(parseColor(attribs)); + } else if (qName.equals("normal")) { + parseLightNormal(attribs); + } else if (qName.equals("lightAttenuation")) { + parseLightAttenuation(attribs); + } + + elementStack.push(qName); + } + + @Override + public void endElement(String uri, String name, String qName) { + 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 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); + } + light = null; + } + assert elementStack.peek().equals(qName); + elementStack.pop(); + } + + @Override + public void characters(char ch[], int start, int length) { + } + + public Object load(AssetInfo info) throws IOException { + try{ + assetManager = info.getManager(); + sceneName = info.getKey().getName(); + String ext = info.getKey().getExtension(); + folderName = info.getKey().getFolder(); + sceneName = sceneName.substring(0, sceneName.length() - ext.length() - 1); + + materialList = (MaterialList) + assetManager.loadAsset(new AssetKey(sceneName+".material")); + + XMLReader xr = XMLReaderFactory.createXMLReader(); + xr.setContentHandler(this); + xr.setErrorHandler(this); + InputStreamReader r = new InputStreamReader(info.openStream()); + xr.parse(new InputSource(r)); + r.close(); + return root; + }catch (SAXException 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 000000000..540e3fcd3 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/SkeletonLoader.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.scene.plugins.ogre; + +import com.jme3.animation.Bone; +import com.jme3.animation.BoneAnimation; +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.File; +import java.io.FileInputStream; +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 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; +import org.xml.sax.helpers.XMLReaderFactory; + +public class SkeletonLoader extends DefaultHandler implements AssetLoader { + + private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + + private AssetManager assetManager; + private Stack elementStack = new Stack(); + + private HashMap indexToBone = new HashMap(); + private HashMap nameToBone = new HashMap(); + + private BoneTrack track; + private ArrayList tracks = new ArrayList(); + + private BoneAnimation animation; + private ArrayList animations; + + private Bone bone; + private Skeleton skeleton; + + private ArrayList times = new ArrayList(); + private ArrayList translations = new ArrayList(); + private ArrayList rotations = new ArrayList(); + + 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 BoneAnimation(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(); + }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")){ + }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 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); + + 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()]); + track.setKeyframes(timesArray, transArray, rotArray); + }else{ + track = null; + } + + times.clear(); + translations.clear(); + rotations.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{ + XMLReader xr = XMLReaderFactory.createXMLReader(); + xr.setContentHandler(this); + xr.setErrorHandler(this); + InputStreamReader r = new InputStreamReader(in); + xr.parse(new InputSource(r)); + if (animations == null){ + animations = new ArrayList(); + } + 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; + } + } + + public Object load(AssetInfo info) throws IOException { + assetManager = info.getManager(); + InputStream in = info.openStream(); + Object obj = load(in); + in.close(); + return obj; + } + +} 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 000000000..d68b7ae16 --- /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; + +/** + * MaterialExtension defines a mapping from an Ogre3D "base" material + * to a jME3 material definition. + */ +public class MaterialExtension { + + private String baseMatName; + private String jmeMatDefName; + private HashMap textureMappings = new HashMap(); + + /** + * 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 000000000..59e1b3dc4 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.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.scene.plugins.ogre.matext; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.scene.plugins.ogre.MaterialLoader; +import com.jme3.texture.Texture; +import java.io.IOException; +import java.util.Scanner; +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 AssetManager assetManager; + private Scanner scan; + private MaterialList list; + private MaterialExtensionSet matExts; + private MaterialExtension matExt; + private String matName; + private Material material; + + private String readString(String end){ + scan.useDelimiter(end); + String str = scan.next(); + scan.useDelimiter("\\p{javaWhitespace}+"); + return str.trim(); + } + + private boolean readExtendingMaterialStatement() throws IOException{ + if (scan.hasNext("set_texture_alias")){ + scan.next(); // skip "set_texture_alias" + + String aliasName = scan.next(); + String texturePath = readString("\n"); + + String jmeParamName = matExt.getTextureMapping(aliasName); + + Texture tex = assetManager.loadTexture(texturePath); + if (tex == null) + throw new IOException("Cannot load texture: " + texturePath); + + material.setTexture(jmeParamName, tex); + + return true; + }else{ + return false; + } + } + + private Material readExtendingMaterial() throws IOException{ + scan.next(); // skip "material" + matName = readString(":").trim(); + scan.next(); + String extendedMat = readString("\\{").trim(); + scan.next(); + + matExt = matExts.getMaterialExtension(extendedMat); + if (matExt == null){ + logger.log(Level.WARNING, "Cannot find MaterialExtension for: {0}. Ignoring material.", extendedMat); + readString("\\}"); + scan.next(); + matExt = null; + return null; + } + + material = new Material(assetManager, matExt.getJmeMatDefName()); + + material.setFloat("Shininess", 16f); + + while (!scan.hasNext("\\}")){ + readExtendingMaterialStatement(); + } + + return material; + } + + public MaterialList load(AssetManager assetManager, MaterialExtensionSet matExts, Scanner scan) throws IOException{ + this.assetManager = assetManager; + this.matExts = matExts; + this.scan = scan; + + list = new MaterialList(); + + if (scan.hasNext("import")){ + scan.nextLine(); // skip this line + } + + toploop: while (scan.hasNext()){ + while (!scan.hasNext("material")){ + if (!scan.hasNext()) + break toploop; + + scan.next(); + } + + Material material = readExtendingMaterial(); + list.put(matName, 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 000000000..c044fb8a6 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.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.scene.plugins.ogre.matext; + +import java.util.HashMap; + +/** + * MaterialExtensionSet 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 extensions + = new HashMap(); + + /** + * 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); + } +} 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 000000000..2bf9f9a17 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.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.scene.plugins.ogre.matext; + +import com.jme3.asset.AssetKey; +import com.jme3.material.MaterialList; + +/** + * OgreMaterialKey allows specifying material extensions, + * which map from Ogre3D base materials to jME3 materials + */ +public class OgreMaterialKey extends AssetKey { + + 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 null to disable this functionality. + * + * @param extension 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 000000000..22dcf5e66 --- /dev/null +++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/package.html @@ -0,0 +1,40 @@ + + + + + + + + + +com.jme3.scene.plugins.ogre.matext allows loading of more advanced +Ogre3D materials that use "base" materials to abstract functionality. +

+E.g. example of an Ogre3D material instance:
+ +import * from "materials/baselighting.material" + +material MyMaterial : BaseLightingMaterial +{ + set_texture_alias MyTexture textures/mytex.png +} + + +

Usage

+ +

+Example code of loading the above material:
+ +MaterialExtensionSet matExts = new MaterialExtensionSet();
+MaterialExtension baseLightExt = new MaterialExtension("BaseLightingMaterial",
+ "Common/MatDefs/Light/Lighting.j3md");
+baseLightExt.setTextureMapping("MyTexture", "m_DiffuseMap");
+matExts.addMaterialExtension(baseLightExt);
+
+OgreMaterialKey matKey = new OgreMaterialKey("materials/mymaterial.material");
+matKey.setMaterialExtensionSet(matExts);
+MaterialList ogreMats = assetManager.loadAsset(matKey);
+
+ + + diff --git a/engine/src/pack/com/jme3/asset/pack/FileRangeChannel.java b/engine/src/pack/com/jme3/asset/pack/FileRangeChannel.java new file mode 100644 index 000000000..fe10a2874 --- /dev/null +++ b/engine/src/pack/com/jme3/asset/pack/FileRangeChannel.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.pack; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; + +public class FileRangeChannel implements ReadableByteChannel { + + private FileChannel channel; + private long position; + private long limit; + + public FileRangeChannel(FileChannel channel, long position, int length){ + if (channel == null) + throw new NullPointerException(); + + if (position < 0 || length <= 0) + throw new IllegalArgumentException(); + + this.channel = channel; + this.position = position; + this.limit = position + length; + } + + public int read(ByteBuffer dst) throws IOException { + if (!channel.isOpen()) + throw new ClosedChannelException(); + + if (dst == null || !dst.hasRemaining()) + return 0; + + int prevLim = dst.limit(); + int toRead = (int) Math.min(dst.remaining(), limit - position); + dst.limit(dst.position() + toRead); + int read = channel.read(dst, position); + position += read; + dst.limit(prevLim); + + return read; + } + + public boolean isOpen() { + return channel.isOpen(); + } + + public void close() throws IOException { + channel.close(); + } + +} diff --git a/engine/src/pack/com/jme3/asset/pack/PackerInputStream.java b/engine/src/pack/com/jme3/asset/pack/PackerInputStream.java new file mode 100644 index 000000000..3c891b9e8 --- /dev/null +++ b/engine/src/pack/com/jme3/asset/pack/PackerInputStream.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.pack; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class PackerInputStream extends FilterInputStream { + + private ProgressListener listener; + + public PackerInputStream(InputStream in, ProgressListener listener){ + super(in); + this.listener = listener; + } + + public int read(byte[] buf, int off, int len) throws IOException{ + int read = super.read(buf, off, len); + if (read > 0) + listener.onProgress(read); + return read; + } + + public int read(byte[] buf) throws IOException{ + int read = super.read(buf); + if (read > 0) + listener.onProgress(read); + return read; + } + + @Override + public int read() throws IOException{ + int read = super.read(); + if (read != -1) + listener.onProgress(1); + return read; + } + + public long skip(long bytes) throws IOException{ + long skipped = super.skip(bytes); + if (skipped > 0) + listener.onProgress((int)skipped); + return skipped; + } + +} diff --git a/engine/src/pack/com/jme3/asset/pack/ProgressListener.java b/engine/src/pack/com/jme3/asset/pack/ProgressListener.java new file mode 100644 index 000000000..f95b11500 --- /dev/null +++ b/engine/src/pack/com/jme3/asset/pack/ProgressListener.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.asset.pack; + +public interface ProgressListener { + public void onMaxProgress(int maxProgress); + public void onProgress(int addProgress); + public void onText(String text); + public void onError(String text, Throwable err); +} diff --git a/engine/src/pack/com/jme3/asset/pack/ReadableBufferChannel.java b/engine/src/pack/com/jme3/asset/pack/ReadableBufferChannel.java new file mode 100644 index 000000000..7dab45522 --- /dev/null +++ b/engine/src/pack/com/jme3/asset/pack/ReadableBufferChannel.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.asset.pack; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ReadableByteChannel; + +/** + * Channel implementation for reading from ByteBuffers. + */ +public class ReadableBufferChannel implements ReadableByteChannel { + + private ByteBuffer bb; + + public ReadableBufferChannel(ByteBuffer bb){ + if (bb == null) + throw new NullPointerException(); + + this.bb = bb; + } + + public int read(ByteBuffer dst) throws IOException { + if (bb == null) + throw new ClosedChannelException(); + + int toRead = Math.min(dst.remaining(), bb.remaining()); + if (toRead == 0) + return -1; // end of stream + + int prevLim = bb.limit(); + int newLim = bb.position() + toRead; + assert newLim <= prevLim; + bb.limit(newLim); + assert bb.remaining() == toRead; + dst.put(bb); + bb.limit(prevLim); + return toRead; + } + + public boolean isOpen() { + return bb != null; + } + + public void close() throws IOException { + bb = null; + } + +} 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 000000000..7ae56cb4f --- /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 000000000..152f51123 --- /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 000000000..ddb40a9f4 --- /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 000000000..ac91d07f1 --- /dev/null +++ b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.frag @@ -0,0 +1,342 @@ + +uniform float m_Shininess; + +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; + + +#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_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 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 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 ); + 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 ); + #endif + #endif + #endif + return diffuseColor; + } + + vec3 calculateNormal(in vec2 texCoord) { + vec3 normal = vec3(0,0,1); + vec4 normalHeight = vec4(0,0,0,0); + vec3 n = vec3(0,0,0); + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef NORMALMAP + normalHeight = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + normalHeight = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + normalHeight = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + normalHeight = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.a; + #endif + + 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 + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + 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 ); + #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 ); + + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + vec4 normalHeight = vec4(0,0,0,0); + + #ifdef NORMALMAP + normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale); + n = (normalHeight.xyz * vec3(2.0) - vec3(1.0)); + n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y)); + n.y = -n.y; + normal += n * alphaBlend.a; + #endif + + 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 + + + //--------------------- + // normal calculations + //--------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) + #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); + + 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 000000000..766e25b3d --- /dev/null +++ b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.j3md @@ -0,0 +1,184 @@ +MaterialDef Terrain Lighting { + + MaterialParameters { + + // use tri-planar mapping + Boolean useTriPlanarMapping + + // Use ward specular instead of phong + Boolean WardIso + + // Ambient color + Color Ambient + + // Diffuse color + Color Diffuse + + // Specular color + Color Specular + + // Specular power/shininess + Float Shininess + + // 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 + + // 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 + WARDISO : WardIso + + DIFFUSEMAP : DiffuseMap + DIFFUSEMAP_1 : DiffuseMap_1 + DIFFUSEMAP_2 : DiffuseMap_2 + DIFFUSEMAP_3 : DiffuseMap_3 + NORMALMAP : NormalMap + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + 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 + } + } + + 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 000000000..eded0ee13 --- /dev/null +++ b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.vert @@ -0,0 +1,98 @@ +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 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); + + 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; + texCoord = inTexCoord; + + vec3 wvPosition = (g_WorldViewMatrix * pos).xyz; + vec3 wvNormal = normalize(g_NormalMatrix * inNormal); + vec3 viewDir = normalize(-wvPosition); + + vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz, g_LightColor.w)); + 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) + 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 + + 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/ProgressMonitor.java b/engine/src/terrain/com/jme3/terrain/ProgressMonitor.java new file mode 100644 index 000000000..43481a2f1 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/ProgressMonitor.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.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 000000000..d2bce1a83 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/Terrain.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 com.jme3.terrain; + +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +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 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); + + /** + * 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); + + /** + * 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(); + + /** + * Tell the terrain system to use/not use Level of Detail algorithms. + * This is allowed to be ignored if a particular implementation cannot support it. + */ + public void useLOD(boolean useLod); + + /** + * Check if the terrain is using LOD techniques. + * If a terrain system only supports enabled LOD, then this + * should always return true. + */ + public boolean isUsingLOD(); + + /** + * 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 in the update (pre or post, up to you) method of your game. + * 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 often the Camera's location + */ + public void update(List location); + + /** + * Get the spatial instance of this Terrain. Right now just used in the + * terrain editor in JMP. + */ + public Spatial getSpatial(); + + /** + * Lock or unlock the meshes of this terrain. + * Locked meshes are uneditable 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. + * This does not necessarily have to guarantee the material + * return is the only material used in the whole terrain structure. + */ + public Material getMaterial(); + + /** + * Calculates the percentage along the terrain (in X-Z plane) that the + * supplied point (worldX,worldY) is, starting from the x=0, z=0 world + * position of the terrain. + * This method must take into account local translations and scale of the terrain. + * Used for painting onto an alpha image for texture splatting. + * + * @param worldX world position on X axis + * @param worldY world position on Z axis + * @return a point (U,V in the range [0,1] ) + */ + public Vector2f getPointPercentagePosition(float worldX, float worldY); + + /** + * 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. + */ + public float getTextureCoordinateScale(); + +} 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 000000000..0d67c7a13 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java @@ -0,0 +1,936 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.FastMath; +import com.jme3.math.Triangle; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +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.BufferGeomap; +import com.jme3.util.BufferUtils; +import java.io.IOException; + +/** + * 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 BufferGeomap { + + public LODGeomap() {} + + public LODGeomap(int size, FloatBuffer heightMap) { + super(heightMap, null, 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 tb = writeTexCoordArray(null, tcOffset, tcScale, offsetAmount, totalSize); + FloatBuffer nb = writeNormalArray(null, scale); + IntBuffer ib = writeIndexArrayLodDiff(null, lod, rightLod, topLod, leftLod, bottomLod); + Mesh m = new Mesh(); + m.setMode(Mode.TriangleStrip); + 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; + } + + + protected void removeNormalBuffer() { + ndata = null; + } + + 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(); + + for (int y = 0; y < getHeight(); 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);//stepScale.x); + float offsetY = offset.y + (offsetAmount * 1.0f);//stepScale.z); + + store.set( ( ((float)x)+offsetX) / (float)totalSize, // calculates percentage of texture here + ( ((float)y)+offsetY) / (float)totalSize ); + 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=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; rowlod) { // 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; ilod) { // 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; ilod) { + buffer.put(getWidth()*(getWidth()-1)); + buffer.put(getWidth()*getWidth() - (getWidth()*lod)+lod); + buffer.put(getWidth()*(getWidth()-1)); + } + for (int col=lod; col= 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/NormalRecalcControl.java b/engine/src/terrain/com/jme3/terrain/geomipmap/NormalRecalcControl.java new file mode 100644 index 000000000..ba13e5238 --- /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/TerrainLodControl.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java new file mode 100644 index 000000000..b9a52da70 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.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.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 java.util.List; + +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.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.terrain.Terrain; +import java.io.IOException; +import java.util.ArrayList; + +/** + * 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. + * + * @author Brent Owens + */ +public class TerrainLodControl extends AbstractControl { + + private TerrainQuad terrain; + private List cameras; + private List cameraLocations = new ArrayList(); + + + public TerrainLodControl() { + + } + + /** + * 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 cameras) { + super((Spatial)terrain); + if (terrain instanceof TerrainQuad) + this.terrain = (TerrainQuad)terrain; + this.cameras = cameras; + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + + } + + @Override + protected void controlUpdate(float tpf) { + //list of cameras for when terrain supports multiple cameras (ie split screen) + + if (cameras != null) { + if (cameraLocations.isEmpty() && !cameras.isEmpty()) { + for (Camera c : cameras) // populate them + cameraLocations.add(c.getLocation()); + } + terrain.update(cameraLocations); + } + } + + public Control cloneForSpatial(Spatial spatial) { + if (spatial instanceof Terrain) { + List cameraClone = new ArrayList(); + if (cameras != null) { + for (Camera c : cameras) + cameraClone.add(c); + } + return new TerrainLodControl((TerrainQuad)spatial, cameraClone); + } + return null; + } + + + public void setCameras(List 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 TerrainQuad) + this.terrain = (TerrainQuad)spatial; + } + + 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/TerrainPatch.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java new file mode 100644 index 000000000..d99c9c30a --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -0,0 +1,1100 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.bounding.Intersection; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.SweepSphere; +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 java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.HashMap; + +import com.jme3.math.FastMath; +import com.jme3.math.Ray; +import com.jme3.math.Triangle; +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.Type; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculatorFactory; +import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; +import com.jme3.util.BufferUtils; +import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.TangentBinormalGenerator.TriangleData; +import java.io.IOException; +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 mip mapping 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 block in relation to (0,0,0) + protected Vector2f offset; + + // amount the block 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 TerrainPatch object. The + * parameters and heightmap data are then processed to generate a + * TriMesh object for rendering. + * + * @param name + * the name of the terrain block. + * @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 block. + */ + 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 TerrainPatch object. The + * parameters and heightmap data are then processed to generate a + * TriMesh object for renderering. + * + * @param name + * the name of the terrain block. + * @param size + * the size of the block. + * @param stepScale + * the scale for the axes. + * @param heightMap + * the height data. + * @param origin + * the origin offset of the block. + * @param totalSize + * the total size of the terrain. (Higher if the block is part of + * a TerrainPage 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); + + FloatBuffer heightBuffer = BufferUtils.createFloatBuffer(size*size); + heightBuffer.put(heightMap); + + geomap = new LODGeomap(size, heightBuffer); + 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; + } + + public FloatBuffer getHeightmap() { + return geomap.getHeightData(); + } + + /** + * 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; + } + + + + + /** + * Delegates to the lodCalculator that was passed in. + * @param locations all possible camera locations + * @param updates update objects that may or may not contain this terrain patch + * @return true if the geometry needs re-indexing + */ + protected boolean calculateLod(List locations, HashMap updates) { + return lodCalculator.calculateLod(locations, updates); + } + + protected void reIndexGeometry(HashMap updated) { + + 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 (lodCalculator.usesVariableLod()) + 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 gridX local to the terrain patch + * @param gridY 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 gridX local to the terrain patch + * @param gridY 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()); + } + + public void setHeight(float x, float z, float height) { + if (x < 0 || z < 0 || x >= size || z >= size) + return; + int idx = (int) (z * size + x); + geomap.getHeightData().put(idx, height); + + FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); + getMesh().clearBuffer(Type.Position); + getMesh().setBuffer(Type.Position, 3, newVertexBuffer); + // normals are updated from the terrain controller on update() + } + + public void adjustHeight(float x, float z, float delta) { + if (x < 0 || z < 0 || x >= size || z >= size) + return; + int idx = (int) (z * size + x); + float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); + + geomap.getHeightData().put(idx, h+delta); + + FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); + getMesh().clearBuffer(Type.Position); + getMesh().setBuffer(Type.Position, 3, newVertexBuffer); + } + + /** + * recalculate all of this normal vectors in this terrain patch + */ + protected void updateNormals() { + FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, stepScale); + getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer); + } + + /** + * 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---* + * | + * * + */ + 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(); + Vector2f rootTex = new Vector2f(); + Vector2f rightTex = new Vector2f(); + Vector2f leftTex = new Vector2f(); + Vector2f topTex = new Vector2f(); + Vector2f bottomTex = new Vector2f(); + Vector3f normal = new Vector3f(); + Vector3f tangent = new Vector3f(); + int[] indexes = new int[]{0,1,2}; + Vector3f[] v = new Vector3f[3]; + Vector2f[] t = new Vector2f[3]; + + int s = this.getSize()-1; + + if (right != null) { // right side + for (int i=0; iNOT rebuild the terrain at all. + * This is mostly used for outside constructors of terrain blocks. + * + * @param offset + * The new texture offset. + */ + public void setOffset(Vector2f offset) { + this.offset = offset; + } + + /** + * Sets the size of this terrain block. Note that this does NOT + * rebuild the terrain at all. This is mostly used for outside constructors + * of terrain blocks. + * + * @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 NOT + * rebuild the terrain at all. This is mostly used for outside constructors + * of terrain blocks. + * + * @param totalSize + * The new total size. + */ + public void setTotalSize(int totalSize) { + this.totalSize = totalSize; + } + + /** + * Sets the step scale of this terrain block's height map. Note that this + * does NOT rebuild the terrain at all. This is mostly used for + * outside constructors of terrain blocks. + * + * @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 NOT + * rebuild the terrain at all. This is mostly used for outside + * constructors of terrain blocks. + * + * @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 LodCalculator getLodCalculator() { + return lodCalculator; + } + + public void setLodCalculator(LodCalculator lodCalculator) { + this.lodCalculator = lodCalculator; + } + + 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= 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 { + 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); + } + + @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); + } + + @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.getHeightData()); + 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; + } + + + +} 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 000000000..998b70351 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java @@ -0,0 +1,1609 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.material.Material; +import java.io.IOException; +import java.util.HashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +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.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.LodCalculatorFactory; +import com.jme3.terrain.geomipmap.lodcalc.LodDistanceCalculatorFactory; +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.BufferUtils; +import com.jme3.util.TangentBinormalGenerator; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +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. + * + * @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 = 1; // 1=upper left, 2=lower left, 3=upper right, 4=lower right + + protected LodCalculatorFactory lodCalculatorFactory; + + + protected List lastCameraLocations; // used for LOD calc + private boolean lodCalcRunning = false; + private boolean usingLOD = true; + private int maxLod = -1; + private HashMap updatedPatches; + private final Object updatePatchesLock = new Object(); + private BoundingBox affectedAreaBBox; // only set in the root quad + + private TerrainPicker picker; + + + private ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread th = new Thread(r); + th.setDaemon(true); + return th; + } + }); + + + public TerrainQuad() { + super("Terrain"); + } + + public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) { + this(name, patchSize, totalSize, heightMap, null); + } + + public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap, LodCalculatorFactory lodCalculatorFactory) { + this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap, lodCalculatorFactory); + } + + public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap, LodCalculatorFactory lodCalculatorFactory) { + this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0, lodCalculatorFactory); + affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size); + fixNormalEdges(affectedAreaBBox); + addControl(new NormalRecalcControl(this)); + } + + protected TerrainQuad(String name, int patchSize, int size, + Vector3f stepScale, float[] heightMap, int totalSize, + Vector2f offset, float offsetAmount, + LodCalculatorFactory lodCalculatorFactory) + { + super(name); + if (!FastMath.isPowerOfTwo(size - 1)) { + throw new RuntimeException("size given: " + size + " Terrain quad sizes may only be (2^N + 1)"); + } + + if (heightMap == null) + heightMap = generateDefaultHeightMap(size); + + this.offset = offset; + this.offsetAmount = offsetAmount; + this.totalSize = totalSize; + this.size = size; + this.patchSize = patchSize; + this.stepScale = stepScale; + this.lodCalculatorFactory = lodCalculatorFactory; + 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 locations) { + + updateLOD(locations); + } + + /** + * 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 locations) { + // update any existing ones that need updating + updateQuadLODs(); + + if (lastCameraLocations != null) { + if (lastCameraLocationsTheSame(locations)) + 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. + } + + UpdateLOD updateLodThread = new UpdateLOD(locations); + executor.execute(updateLodThread); + } + + private synchronized boolean isLodCalcRunning() { + return lodCalcRunning; + } + + private synchronized void setLodCalcRunning(boolean running) { + lodCalcRunning = running; + } + + private List cloneVectorList(List locations) { + List cloned = new ArrayList(); + for(Vector3f l : locations) + cloned.add(l.clone()); + return cloned; + } + + private boolean lastCameraLocationsTheSame(List 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; + } + + public Spatial getSpatial() { + return this; + } + + 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() { + // 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(); + } else if (child instanceof TerrainPatch) { + return ((TerrainPatch)child).getMaterial(); + } + } + } + return null; + } + + public float getTextureCoordinateScale() { + return 1f/(float)totalSize; + } + + /** + * Calculates the LOD of all child terrain patches. + */ + private class UpdateLOD implements Runnable { + private List camLocations; + + UpdateLOD(List location) { + camLocations = location; + } + + 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 updated = new HashMap(); + boolean lodChanged = calculateLod(camLocations, updated); // '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); + + 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 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 (true) + // return; + if (updatedPatches == null || updatedPatches.isEmpty()) + return; + + //TODO do the actual geometry update here + for (UpdatedTerrainPatch utp : updatedPatches.values()) { + utp.updateAll(); + } + + updatedPatches.clear(); + } + } + + protected boolean calculateLod(List location, HashMap updates) { + + 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); + if (b) + lodChanged = true; + } else if (child instanceof TerrainPatch) { + boolean b = ((TerrainPatch) child).calculateLod(location, updates); + if (b) + lodChanged = true; + } + } + } + + return lodChanged; + } + + protected synchronized void findNeighboursLod(HashMap 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()); + } + + } + } + } + } + + /** + * 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 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.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 updated) { + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).reIndexPages(updated); + } else if (child instanceof TerrainPatch) { + ((TerrainPatch) child).reIndexGeometry(updated); + } + } + } + } + + /** + * split divides the heightmap data for four children. The + * children are either pages or blocks. 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 blocks are created, otherwise, pages 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); + } + + } + + /** + * createQuadPage generates four new pages from this page. + */ + protected void createQuad(int blockSize, float[] heightMap) { + // create 4 terrain pages + int quarterSize = size >> 2; + + int split = (size + 1) >> 1; + + Vector2f tempOffset = new Vector2f(); + offsetAmount += quarterSize; + + if (lodCalculatorFactory == null) + lodCalculatorFactory = new LodDistanceCalculatorFactory(); // set a default one + + // 1 upper left + 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 page1 = new TerrainQuad(getName() + "Quad1", blockSize, + split, stepScale, heightBlock1, totalSize, tempOffset, + offsetAmount, lodCalculatorFactory); + page1.setLocalTranslation(origin1); + page1.quadrant = 1; + this.attachChild(page1); + + // 2 lower left + 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 page2 = new TerrainQuad(getName() + "Quad2", blockSize, + split, stepScale, heightBlock2, totalSize, tempOffset, + offsetAmount, lodCalculatorFactory); + page2.setLocalTranslation(origin2); + page2.quadrant = 2; + this.attachChild(page2); + + // 3 upper right + 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 page3 = new TerrainQuad(getName() + "Quad3", blockSize, + split, stepScale, heightBlock3, totalSize, tempOffset, + offsetAmount, lodCalculatorFactory); + page3.setLocalTranslation(origin3); + page3.quadrant = 3; + this.attachChild(page3); + // // + // 4 lower right + 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 page4 = new TerrainQuad(getName() + "Quad4", blockSize, + split, stepScale, heightBlock4, totalSize, tempOffset, + offsetAmount, lodCalculatorFactory); + page4.setLocalTranslation(origin4); + page4.quadrant = 4; + this.attachChild(page4); + + } + + 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.1f)); + attachChild(debug); + debug.setLocalTranslation(child.getLocalTranslation()); + debug.setCullHint(CullHint.Never); + debug.setMaterial(mat); + } + } + } + /** + * createQuadBlock creates four child blocks from this page. + */ + protected void createQuadPatch(float[] heightMap) { + // create 4 terrain blocks + int quarterSize = size >> 2; + int halfSize = size >> 1; + int split = (size + 1) >> 1; + + if (lodCalculatorFactory == null) + lodCalculatorFactory = new LodDistanceCalculatorFactory(); // set a default one + + offsetAmount += quarterSize; + + // 1 upper 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(lodCalculatorFactory); + TangentBinormalGenerator.generate(patch1); + + // 2 lower 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(lodCalculatorFactory); + TangentBinormalGenerator.generate(patch2); + + // 3 upper 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(lodCalculatorFactory); + TangentBinormalGenerator.generate(patch3); + + // 4 lower 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(lodCalculatorFactory); + 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), 0.5f, Float.MAX_VALUE, 0.5f); // unit length + } else { + // adjust size of box to be larger + affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 0.5f, Float.MAX_VALUE, 0.5f)); + } + } + + protected boolean needToRecalculateNormals() { + return affectedAreaBBox != null; + } + + public float getHeightmapHeight(Vector2f xz) { + // offset + int x = Math.round((xz.x / getLocalScale().x) + totalSize / 2); + int z = Math.round((xz.y / getLocalScale().z) + totalSize / 2); + + 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; + } + + + public float getHeight(Vector2f xz) { + // offset + float x = (float)((xz.x / getLocalScale().x) + (float)totalSize / 2f); + float z = (float)((xz.y / getLocalScale().z) + (float)totalSize / 2f); + return getHeight(x, z, xz); + } + + /* + * 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, Vector2f xz) { + float topLeft = getHeightmapHeight((int)FastMath.floor(x), (int)FastMath.ceil(z)); + float topRight = getHeightmapHeight((int)FastMath.ceil(x), (int)FastMath.ceil(z)); + float bottomLeft = getHeightmapHeight((int)FastMath.floor(x), (int)FastMath.floor(z)); + float bottomRight = getHeightmapHeight((int)FastMath.ceil(x), (int)FastMath.floor(z)); + + // create a vertical, down-facing, ray and get the height from that + float max = Math.max(Math.max(Math.max(topLeft, topRight), bottomRight),bottomLeft); + max = max*getWorldScale().y; + Ray ray = new Ray(new Vector3f(xz.x,max+10f,xz.y), new Vector3f(0,-1,0).normalizeLocal()); + CollisionResults cr = new CollisionResults(); + int num = this.collideWith(ray, cr); + if (num > 0) + return cr.getClosestCollision().getContactPoint().y; + else + return 0; + } + + + // the coord calculations should be the same as getHeight() + public void setHeight(Vector2f xz, float height) { + // offset + int x = Math.round((xz.x / getLocalScale().x) + totalSize / 2); + int z = Math.round((xz.y / getLocalScale().z) + totalSize / 2); + + setHeight(x, z, height); // adjust the actual mesh + + setNormalRecalcNeeded(xz); + } + + protected void setHeight(int x, int z, float newVal) { + 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) { + ((TerrainQuad) spat).setHeight(col, row, newVal); + } else if (spat instanceof TerrainPatch) { + ((TerrainPatch) spat).setHeight(col, row, newVal); + } + } + + } + } + } + + protected boolean isPointOnTerrain(int x, int z) { + return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); + } + + public Vector2f getPointPercentagePosition(float worldX, float worldY) { + Vector2f uv = new Vector2f(worldX,worldY); + uv.subtractLocal(getLocalTranslation().x, getLocalTranslation().z); // center it on 0,0 + uv.addLocal(totalSize/2, totalSize/2); // shift the bottom left corner up to 0,0 + uv.divideLocal(totalSize); // get the location as a percentage + + return uv; + } + + + public void adjustHeight(Vector2f xz, float delta) { + int x = Math.round((xz.x / getLocalScale().x) + totalSize / 2); + int z = Math.round((xz.y / getLocalScale().z) + totalSize / 2); + + if (!isPointOnTerrain(x,z)) + return; + + adjustHeight(x, z,delta); + + setNormalRecalcNeeded(xz); + } + + protected void adjustHeight(int x, int z, float delta) { + 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) { + ((TerrainQuad) spat).adjustHeight(col, row, delta); + } else if (spat instanceof TerrainPatch) { + ((TerrainPatch) spat).adjustHeight(col, row, delta); + } + } + + } + } + } + + + // 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 page 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 page 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 page 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 page 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 page 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 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); + 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 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 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); + lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); + + // the terrain is re-built on load, so we need to run this once + //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size); + //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); + } + + @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; + quadClone.stepScale = stepScale.clone(); + quadClone.offset = offset.clone(); + quadClone.offsetAmount = offsetAmount; + quadClone.quadrant = quadrant; + quadClone.lodCalculatorFactory = lodCalculatorFactory.clone(); + TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class); + if (lodControl != null && !(getParent() instanceof TerrainQuad)) { + lodControl.setTerrain(quadClone); // set println in controller update to see if it is updating + } + 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 void useLOD(boolean useLod) { + usingLOD = useLod; + } + + public boolean isUsingLOD() { + return usingLOD; + } + + public int getPatchSize() { + return patchSize; + } + + public int getTotalSize() { + return totalSize; + } + + + public float[] getHeightMap() { + + //if (true) + // return heightMap; + + float[] hm = null; + int length = ((size-1)/2)+1; + int area = size*size; + hm = new float[area]; + + if (getChildren() != null) { + 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 = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap()); + else if(((TerrainPatch) s).getQuadrant() == 2) + bl = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap()); + else if(((TerrainPatch) s).getQuadrant() == 3) + ur = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap()); + else if(((TerrainPatch) s).getQuadrant() == 4) + br = BufferUtils.getFloatArray(((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 locations, HashMap updates) { + float distance = getCenterLocation().distance(locations.get(0)); + + // go through each lod level to find the one we are in + for (int i = 0; i <= terrainPatch.getMaxLod(); i++) { + if (distance < lodThresholdCalculator.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; + } + + public Vector3f getCenterLocation() { + 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 setTerrainPatch(TerrainPatch terrainPatch) { + this.terrainPatch = terrainPatch; + } + + protected LodThreshold getLodThreshold() { + return lodThresholdCalculator; + } + + protected void setLodThreshold(LodThreshold lodThresholdCalculator) { + this.lodThresholdCalculator = lodThresholdCalculator; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(lodThresholdCalculator, "lodThresholdCalculator", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + lodThresholdCalculator = (LodThreshold) ic.readSavable("lodThresholdCalculator", null); + } + + @Override + public LodCalculator clone() { + DistanceLodCalculator clone = new DistanceLodCalculator(); + clone.lodThresholdCalculator = lodThresholdCalculator.clone(); + + return clone; + } + + @Override + public String toString() { + return "DistanceLodCalculator " + lodThresholdCalculator.toString(); + } + + public boolean usesVariableLod() { + return 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 000000000..2f37c596f --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.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.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 void setTerrainPatch(TerrainPatch terrainPatch); + public boolean calculateLod(List locations, HashMap updates); + + public LodCalculator clone(); + + /** + * 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 000000000..7785a1966 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.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.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 + */ +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 000000000..f114a72e7 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.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.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 + */ +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) { + if (lodThreshold == null) + lodThreshold = new SimpleLodThreshold(terrainPatch.getSize(), lodThresholdSize); + return new DistanceLodCalculator(terrainPatch, lodThreshold); + } + + 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 000000000..3d8d85592 --- /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. + */ +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); + p.setTerrainPatch(terrainPatch); + 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 000000000..bf09b00b1 --- /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 000000000..15a743a99 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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(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 void setTerrainPatch(TerrainPatch terrainPatch) { + patch = terrainPatch; + } + + public boolean calculateLod(List locations, HashMap 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(); + + // 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() { + 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; + } + +} 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 000000000..3dd92dd5f --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.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.terrain.geomipmap.lodcalc; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +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(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 000000000..c358aae10 --- /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 000000000..9f044c6ef --- /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 pickData = new ArrayList(); + 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 000000000..8d8a53d2c --- /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 000000000..7ec316c13 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPickData.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.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; + } + +} 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 000000000..67df8e782 --- /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 000000000..814e04a58 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/heightmap/AbstractHeightMap.java @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +/** + * AbstractHeightMap 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 load 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; + + /** + * unloadHeightMap clears the data of the height map. This + * insures it is ready for reloading. + */ + public void unloadHeightMap() { + heightData = null; + } + + /** + * setHeightScale 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; + } + + /** + * setHeightAtPoint 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; + } + + /** + * setSize 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; + } + + /** + * setFilter 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; + } + + /** + * getTrueHeightAtPoint 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)]; + } + + /** + * getScaledHeightAtPoint 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); + } + + /** + * getInterpolatedHeight 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); + } + + /** + * getHeightMap 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; igetSize 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; + } + + /** + * save 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; + } + + /** + * normalizeTerrain takes the current terrain data and + * converts it to values between 0 and value. + * + * @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; + } + } + } + + /** + * erodeTerrain 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; + } + 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; + } + } + } + + /** + * Smooth the terrain. For each node, its 8 neighbors heights + * are averaged and will participate in the node new height + * by a factor np between 0 and 1 + * + * @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) { + if (np < 0 || np > 1) { + return; + } + int[] dxs = new int[]{-1, 0, 1, 1, 1, 0, -1, -1}; + int[] dys = new int[]{-1, -1, -1, 0, 1, 1, 1, 0}; + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + int neighNumber = 0; + float neighAverage = 0; + for (int d = 0; d < 8; d++) { + int i = x + dxs[d]; + int j = y + dys[d]; + if (i < 0 || i > size) { + continue; + } + if (j < 0 || j > size) { + continue; + } + neighNumber++; + neighAverage += heightData[i + j * 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 000000000..7a258349f --- /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; + +/** + * CombinerHeightMap 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(); + } + + /** + * setFactors 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; + } + + /** + * setHeightMaps 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; + } + + /** + * setMode 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; + } + + /** + * load 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 000000000..da7d78265 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/heightmap/FaultHeightMap.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 faultHeight 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 faultHeight height to modify + */ + 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 000000000..ec2dfdfe0 --- /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; + +/** + * FluidSimHeightMap 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 Terrain Generation Using Fluid Simulation + * @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 000000000..1bbed3613 --- /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 { + + /** + * getHeightMap returns the entire grid of height data. + * + * @return the grid of height data. + */ + float[] getHeightMap(); + + float[] getScaledHeightMap(); + + /** + * getInterpolatedHeight 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); + + /** + * getScaledHeightAtPoint 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); + + /** + * getSize 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(); + + /** + * getTrueHeightAtPoint 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); + + /** + * load populates the height map data. This is dependent on + * the subclass's implementation. + * + * @return true if the load was successful, false otherwise. + */ + boolean load(); + + /** + * setHeightAtPoint 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); + + /** + * setHeightScale 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); + + /** + * setFilter 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; + + /** + * setSize 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; + + /** + * unloadHeightMap 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/HillHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/HillHeightMap.java new file mode 100644 index 000000000..4a6a46252 --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/heightmap/HillHeightMap.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.terrain.heightmap; + +import java.util.Random; +import java.util.logging.Logger; + +/** + * HillHeightMap 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 Hill Algorithm + */ +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 flattening + * the power of flattening done, 1 means none + * @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 + * @param flattening + * the power of flattening done, 1 means none + * @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 Random + * 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 + * @throw 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 000000000..b9633fc3e --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMap.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.PixelGrabber; +import java.awt.image.WritableRaster; + +/** + * ImageBasedHeightMap 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 { + + static protected class ImageConverter { + + // Posted by DrLaszloJamf to Java Technology Forums + // + // Copyright 1994-2004 Sun Microsystems, Inc. All Rights Reserved. + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions + // are met: + // + // Redistribution of source code must retain the above copyright notice, + // this list of conditions and the following disclaimer. + // + // Redistribution in binary form must reproduce the above copyright + // notice, this list of conditions and the following disclaimer in the + // documentation and/or other materials provided with the distribution. + // + // Neither the name of Sun Microsystems, Inc. or the names of + // contributors may be used to endorse or promote products derived from + // this software without specific prior written permission. + // + // This software is provided "AS IS," without a warranty of any kind. + // ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + // INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + // PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + // MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + // ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + // DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN + // OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR + // FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + // DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + // ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + // SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + // + // + // You acknowledge that this software is not designed, licensed or + // intended for use in the design, construction, operation or + // maintenance of any nuclear facility. + + // preserves image's colormodel. Assumes image is loaded + public static BufferedImage toBufferedImage(Image image) { + if (image instanceof BufferedImage) return (BufferedImage) image; + ColorModel cm = getColorModel(image); + int width = image.getWidth(null); + int height = image.getHeight(null); + return copy(createBufferedImage(cm, width, height), image); + } + + public static BufferedImage toBufferedImage(Image image, int type) { + if (image instanceof BufferedImage + && ((BufferedImage) image).getType() == type) + return (BufferedImage) image; + int width = image.getWidth(null); + int height = image.getHeight(null); + return copy(new BufferedImage(width, height, type), image); + } + + // Returns target. Assumes source is loaded + public static BufferedImage copy(BufferedImage target, Image source) { + Graphics2D g = target.createGraphics(); + g.drawImage(source, 0, 0, null); + g.dispose(); + return target; + } + + public static ColorModel getColorModel(Image image) { + try { + PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); + pg.grabPixels(); + return pg.getColorModel(); + } catch (InterruptedException e) { + throw new RuntimeException("Unexpected interruption", e); + } + } + + public static BufferedImage createBufferedImage(ColorModel cm, int w, + int h) { + WritableRaster raster = cm.createCompatibleWritableRaster(w, h); + boolean isRasterPremultiplied = cm.isAlphaPremultiplied(); + return new BufferedImage(cm, raster, isRasterPremultiplied, null); + } + } + + + protected Image colorImage; + protected float dampen = 1.0f; + + + /** + * 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, 1.0f); + } + + public ImageBasedHeightMap(Image colorImage, float dampen) { + super(); + this.colorImage = colorImage; + this.dampen = dampen; + } + + /** + * Loads the image data from top left to bottom right + */ + public boolean load() { + return load(false, false); + } + + public boolean load(boolean flipX, boolean flipY) { + + // FUTURE: Rescale image if not square? + BufferedImage colorBufferedImage = ImageConverter.toBufferedImage( + colorImage, BufferedImage.TYPE_3BYTE_BGR); + + boolean hasAlpha = colorBufferedImage.getColorModel().hasAlpha(); + + int imageWidth = colorBufferedImage.getWidth(); + int imageHeight = colorBufferedImage.getHeight(); + + if (imageWidth != imageHeight) + throw new RuntimeException("imageWidth: " + imageWidth + + " != imageHeight: " + imageHeight); + + size = imageWidth; + + byte data[] = (byte[]) colorBufferedImage.getRaster().getDataElements( + 0, 0, imageWidth, imageHeight, null); + + int bytesPerPixel = 3; + int blueBase = 0; + if (hasAlpha) { + bytesPerPixel = 4; + blueBase = 1; + } + + heightData = new float[(imageWidth * imageHeight)]; + + int startW = 0; + int endW = imageWidth-1; + if (flipX) { + startW = imageWidth-1; + endW = 0; + } + int startH = imageHeight-1; + int endH = 0; + if (flipY) { + startH = 0; + endH = imageHeight-1; + } + + 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 * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + heightData[index++] = grayscale; + //heightData[index++] = calculateHeight(red,green,blue); + } + } else { + for (int w = 0; w < imageWidth; ++w) { + int baseIndex = (h * imageWidth * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + heightData[index++] = grayscale; + //heightData[index++] = calculateHeight(red,green,blue); + + } + } + } + } else { + for (int h = imageHeight - 1; h >= 0; --h) { + if (flipX) { + for (int w = imageWidth - 1; w >= 0; --w) { + int baseIndex = (h * imageWidth * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + heightData[index++] = grayscale; + //heightData[index++] = calculateHeight(red,green,blue); + } + } else { + for (int w = 0; w < imageWidth; ++w) { + int baseIndex = (h * imageWidth * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + heightData[index++] = grayscale; + //heightData[index++] = calculateHeight(red,green,blue); + } + } + } + } + + /*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 * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + + heightData[index++] = grayscale; + } + } else { + for (int w = 0; w < imageWidth; ++w) { + int baseIndex = (h * imageWidth * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + + heightData[index++] = grayscale; + } + } + } + } else { + for (int h = imageHeight-1; h >= 0; --h) { + if (flipX) { + for (int w = imageWidth-1; w >= 0; --w) { + int baseIndex = (h * imageWidth * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + + heightData[index++] = grayscale; + } + } else { + for (int w = 0; w < imageWidth; ++w) { + int baseIndex = (h * imageWidth * bytesPerPixel) + + (w * bytesPerPixel) + blueBase; + float blue = data[baseIndex] >= 0 ? data[baseIndex] + : (256 + (data[baseIndex])); + float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1] + : (256 + (data[baseIndex + 1])); + float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2] + : (256 + (data[baseIndex + 2])); + + float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen); + + heightData[index++] = grayscale; + } + } + } + }*/ + + return true; + } +} \ No newline at end of file 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 000000000..acb2b1226 --- /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; + +/** + * MidpointDisplacementHeightMap 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 -range + * and range. The range parameter is multiplied by + * the persistence 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 -range + * and range. The range parameter is multiplied by + * the persistence 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/ParticleDepositionHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java new file mode 100644 index 000000000..63dccc6be --- /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; + +/** + * ParticleDepositionHeightMap 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(); + } + + /** + * load generates the heightfield using the Particle Deposition + * algorithm. load uses the latest attributes, so a call + * to load 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; + } + + /** + * setJumps sets the number of jumps or peaks that will + * be created during the next call to load. + * @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; + } + + /** + * setPeakWalk 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; + } + + /** + * setCaldera 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; + } + + /** + * setMaxParticles 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; + } + + /** + * setMinParticles 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 000000000..b838d6d4a --- /dev/null +++ b/engine/src/terrain/com/jme3/terrain/heightmap/RawHeightMap.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.BufferedInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.logging.Logger; + +/** + * RawHeightMap 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 RawHeightMap 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(); + } + + /** + * load 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; + } + + /** + * setFilename sets the file to use for the RAW data. A call + * to load 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); + } + } + + /** + * setHeightStream sets the stream to use for the RAW data. A call + * to load 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/test-data/Effects/Explosion/Debris.png b/engine/src/test-data/Effects/Explosion/Debris.png new file mode 100644 index 000000000..c6eab7bac Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/Debris.png differ diff --git a/engine/src/test-data/Effects/Explosion/flame.png b/engine/src/test-data/Effects/Explosion/flame.png new file mode 100644 index 000000000..d3a31f792 Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/flame.png differ diff --git a/engine/src/test-data/Effects/Explosion/flash.png b/engine/src/test-data/Effects/Explosion/flash.png new file mode 100644 index 000000000..d24091751 Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/flash.png differ diff --git a/engine/src/test-data/Effects/Explosion/roundspark.png b/engine/src/test-data/Effects/Explosion/roundspark.png new file mode 100644 index 000000000..bb2b5d84f Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/roundspark.png differ diff --git a/engine/src/test-data/Effects/Explosion/shockwave.png b/engine/src/test-data/Effects/Explosion/shockwave.png new file mode 100644 index 000000000..62856bd1c Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/shockwave.png differ diff --git a/engine/src/test-data/Effects/Explosion/smoketrail.png b/engine/src/test-data/Effects/Explosion/smoketrail.png new file mode 100644 index 000000000..5b965182a Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/smoketrail.png differ diff --git a/engine/src/test-data/Effects/Explosion/spark.png b/engine/src/test-data/Effects/Explosion/spark.png new file mode 100644 index 000000000..fa2d25a04 Binary files /dev/null and b/engine/src/test-data/Effects/Explosion/spark.png differ diff --git a/engine/src/test-data/Effects/Smoke/Smoke.png b/engine/src/test-data/Effects/Smoke/Smoke.png new file mode 100644 index 000000000..ec46564ba Binary files /dev/null and b/engine/src/test-data/Effects/Smoke/Smoke.png differ diff --git a/engine/src/test-data/Interface/Logo/Logo.j3m b/engine/src/test-data/Interface/Logo/Logo.j3m new file mode 100644 index 000000000..7c086b6d8 --- /dev/null +++ b/engine/src/test-data/Interface/Logo/Logo.j3m @@ -0,0 +1,5 @@ +Material jME Logo : Common/MatDefs/Misc/Unshaded.j3md { + MaterialParameters { + ColorMap : Interface/Logo/Monkey.jpg + } +} \ No newline at end of file diff --git a/engine/src/test-data/Interface/Logo/Monkey.jpg b/engine/src/test-data/Interface/Logo/Monkey.jpg new file mode 100644 index 000000000..cfe465de1 Binary files /dev/null and b/engine/src/test-data/Interface/Logo/Monkey.jpg differ diff --git a/engine/src/test-data/Interface/Logo/Monkey.png b/engine/src/test-data/Interface/Logo/Monkey.png new file mode 100644 index 000000000..e1c8c3d8b Binary files /dev/null and b/engine/src/test-data/Interface/Logo/Monkey.png differ diff --git a/engine/src/test-data/Interface/icons/SmartMonkey128.png b/engine/src/test-data/Interface/icons/SmartMonkey128.png new file mode 100644 index 000000000..555c72276 Binary files /dev/null and b/engine/src/test-data/Interface/icons/SmartMonkey128.png differ diff --git a/engine/src/test-data/Interface/icons/SmartMonkey16.png b/engine/src/test-data/Interface/icons/SmartMonkey16.png new file mode 100644 index 000000000..34a91c428 Binary files /dev/null and b/engine/src/test-data/Interface/icons/SmartMonkey16.png differ diff --git a/engine/src/test-data/Interface/icons/SmartMonkey256.png b/engine/src/test-data/Interface/icons/SmartMonkey256.png new file mode 100644 index 000000000..02417f986 Binary files /dev/null and b/engine/src/test-data/Interface/icons/SmartMonkey256.png differ diff --git a/engine/src/test-data/Interface/icons/SmartMonkey32.png b/engine/src/test-data/Interface/icons/SmartMonkey32.png new file mode 100644 index 000000000..ee5e2093c Binary files /dev/null and b/engine/src/test-data/Interface/icons/SmartMonkey32.png differ diff --git a/engine/src/test-data/Models/Buggy/Buggy.j3m b/engine/src/test-data/Models/Buggy/Buggy.j3m new file mode 100644 index 000000000..cc5784fa3 --- /dev/null +++ b/engine/src/test-data/Models/Buggy/Buggy.j3m @@ -0,0 +1,9 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Models/Buggy/buggy_diffuse.png + GlowMap : Models/Buggy/buggy_glow.jpg + SpecularMap : Models/Buggy/buggy_specular.png + Shininess : 10 + NormalMap : Models/Buggy/buggy_normals.png + } +} diff --git a/engine/src/test-data/Models/Buggy/Buggy.j3o b/engine/src/test-data/Models/Buggy/Buggy.j3o new file mode 100644 index 000000000..8d66fa071 Binary files /dev/null and b/engine/src/test-data/Models/Buggy/Buggy.j3o differ diff --git a/engine/src/test-data/Models/Buggy/buggy_diffuse.png b/engine/src/test-data/Models/Buggy/buggy_diffuse.png new file mode 100644 index 000000000..bed86cd4d Binary files /dev/null and b/engine/src/test-data/Models/Buggy/buggy_diffuse.png differ diff --git a/engine/src/test-data/Models/Buggy/buggy_glow.jpg b/engine/src/test-data/Models/Buggy/buggy_glow.jpg new file mode 100644 index 000000000..952c66a15 Binary files /dev/null and b/engine/src/test-data/Models/Buggy/buggy_glow.jpg differ diff --git a/engine/src/test-data/Models/Buggy/buggy_normals.png b/engine/src/test-data/Models/Buggy/buggy_normals.png new file mode 100644 index 000000000..dd9feefd7 Binary files /dev/null and b/engine/src/test-data/Models/Buggy/buggy_normals.png differ diff --git a/engine/src/test-data/Models/Buggy/buggy_specular.png b/engine/src/test-data/Models/Buggy/buggy_specular.png new file mode 100644 index 000000000..3c9369ec4 Binary files /dev/null and b/engine/src/test-data/Models/Buggy/buggy_specular.png differ diff --git a/engine/src/test-data/Models/Elephant/Elephant.j3m b/engine/src/test-data/Models/Elephant/Elephant.j3m new file mode 100644 index 000000000..4a58ec394 --- /dev/null +++ b/engine/src/test-data/Models/Elephant/Elephant.j3m @@ -0,0 +1,8 @@ +Material ElephantBody : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 16.0 + DiffuseMap: Models/Elephant/Elephant.jpg + NormalMap: Models/Elephant/Elephant_normal.jpg + + } +} \ No newline at end of file diff --git a/engine/src/test-data/Models/Elephant/Elephant.jpg b/engine/src/test-data/Models/Elephant/Elephant.jpg new file mode 100644 index 000000000..b4b4014d1 Binary files /dev/null and b/engine/src/test-data/Models/Elephant/Elephant.jpg differ diff --git a/engine/src/test-data/Models/Elephant/Elephant.material b/engine/src/test-data/Models/Elephant/Elephant.material new file mode 100644 index 000000000..268d6cc4c --- /dev/null +++ b/engine/src/test-data/Models/Elephant/Elephant.material @@ -0,0 +1,76 @@ +material ElphSkin +{ + technique + { + pass + { + ambient 0 0 0 1 + diffuse 1 1 1 1 + specular 1 1 1 25.6 + emissive 0 0 0 + + texture_unit + { + texture Elephant.jpg + tex_coord_set 0 + } + } + } +} +material Ele_Tuskh +{ + technique + { + pass + { + ambient 0 0 0 1 + diffuse 1 1 1 1 + specular 1 1 1 25.6 + emissive 0 0 0 + + texture_unit + { + texture ElephantTusk.jpg + tex_coord_set 0 + } + } + } +} +material leftEyeShader +{ + technique + { + pass + { + ambient 0 0 0 1 + diffuse 1 1 1 1 + specular 0.5 0.5 0.5 25.6 + emissive 0 0 0 + + texture_unit + { + texture ElephantEye.jpg + tex_coord_set 0 + } + } + } +} +material rightEyeShader +{ + technique + { + pass + { + ambient 0 0 0 1 + diffuse 1 1 1 1 + specular 0.5 0.5 0.5 25.6 + emissive 0 0 0 + + texture_unit + { + texture ElephantEye.jpg + tex_coord_set 0 + } + } + } +} diff --git a/engine/src/test-data/Models/Elephant/Elephant.mesh.xml b/engine/src/test-data/Models/Elephant/Elephant.mesh.xml new file mode 100644 index 000000000..8bffa1a5e --- /dev/null +++ b/engine/src/test-data/Models/Elephant/Elephant.mesh.xml @@ -0,0 +1,62928 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Elephant/Elephant.skeletonxml b/engine/src/test-data/Models/Elephant/Elephant.skeletonxml new file mode 100644 index 000000000..79da840d4 --- /dev/null +++ b/engine/src/test-data/Models/Elephant/Elephant.skeletonxml @@ -0,0 +1,1422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Elephant/ElephantEye.jpg b/engine/src/test-data/Models/Elephant/ElephantEye.jpg new file mode 100644 index 000000000..6eb7b18bc Binary files /dev/null and b/engine/src/test-data/Models/Elephant/ElephantEye.jpg differ diff --git a/engine/src/test-data/Models/Elephant/ElephantTusk.jpg b/engine/src/test-data/Models/Elephant/ElephantTusk.jpg new file mode 100644 index 000000000..e9d534d34 Binary files /dev/null and b/engine/src/test-data/Models/Elephant/ElephantTusk.jpg differ diff --git a/engine/src/test-data/Models/Elephant/Elephant_normal.jpg b/engine/src/test-data/Models/Elephant/Elephant_normal.jpg new file mode 100644 index 000000000..7df4c4355 Binary files /dev/null and b/engine/src/test-data/Models/Elephant/Elephant_normal.jpg differ diff --git a/engine/src/test-data/Models/Ferrari/Car.jpg b/engine/src/test-data/Models/Ferrari/Car.jpg new file mode 100644 index 000000000..56d3f63e5 Binary files /dev/null and b/engine/src/test-data/Models/Ferrari/Car.jpg differ diff --git a/engine/src/test-data/Models/Ferrari/Car.material b/engine/src/test-data/Models/Ferrari/Car.material new file mode 100644 index 000000000..f6c9b99be --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/Car.material @@ -0,0 +1,15 @@ +material fskin.002/SOLID/TEX/fskin.jpg +{ + technique + { + pass + { + diffuse 0.505882 0.505882 0.505882 + specular 0.500000 0.500000 0.500000 12.500000 + texture_unit + { + texture Car.jpg + } + } + } +} diff --git a/engine/src/test-data/Models/Ferrari/Car.mesh.xml b/engine/src/test-data/Models/Ferrari/Car.mesh.xml new file mode 100644 index 000000000..8668bda73 --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/Car.mesh.xml @@ -0,0 +1,5083 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Ferrari/Car.scene b/engine/src/test-data/Models/Ferrari/Car.scene new file mode 100644 index 000000000..5ff38600d --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/Car.scene @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Ferrari/WheelBackLeft.mesh.xml b/engine/src/test-data/Models/Ferrari/WheelBackLeft.mesh.xml new file mode 100644 index 000000000..75999b2cc --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/WheelBackLeft.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Ferrari/WheelBackRight.mesh.xml b/engine/src/test-data/Models/Ferrari/WheelBackRight.mesh.xml new file mode 100644 index 000000000..d0d7f1f4f --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/WheelBackRight.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Ferrari/WheelFrontLeft.mesh.xml b/engine/src/test-data/Models/Ferrari/WheelFrontLeft.mesh.xml new file mode 100644 index 000000000..57e308c4f --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/WheelFrontLeft.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Ferrari/WheelFrontRight.mesh.xml b/engine/src/test-data/Models/Ferrari/WheelFrontRight.mesh.xml new file mode 100644 index 000000000..6dc7ee46f --- /dev/null +++ b/engine/src/test-data/Models/Ferrari/WheelFrontRight.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/HoverTank/Tank2.mesh.xml b/engine/src/test-data/Models/HoverTank/Tank2.mesh.xml new file mode 100644 index 000000000..10378f20d --- /dev/null +++ b/engine/src/test-data/Models/HoverTank/Tank2.mesh.xml @@ -0,0 +1,80192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/HoverTank/tankFinalExport.blend b/engine/src/test-data/Models/HoverTank/tankFinalExport.blend new file mode 100644 index 000000000..17aceb474 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tankFinalExport.blend differ diff --git a/engine/src/test-data/Models/HoverTank/tank_diffuse.png b/engine/src/test-data/Models/HoverTank/tank_diffuse.png new file mode 100644 index 000000000..cc19ff757 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_diffuse.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_diffuse_LowRes.png b/engine/src/test-data/Models/HoverTank/tank_diffuse_LowRes.png new file mode 100644 index 000000000..0d5374528 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_diffuse_LowRes.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_glow_map.png b/engine/src/test-data/Models/HoverTank/tank_glow_map.png new file mode 100644 index 000000000..75124950e Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_glow_map.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_glow_map_highres.png b/engine/src/test-data/Models/HoverTank/tank_glow_map_highres.png new file mode 100644 index 000000000..9cacf2777 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_glow_map_highres.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_highRes.j3m b/engine/src/test-data/Models/HoverTank/tank_highRes.j3m new file mode 100644 index 000000000..baee843ad --- /dev/null +++ b/engine/src/test-data/Models/HoverTank/tank_highRes.j3m @@ -0,0 +1,13 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + SpecularMap : Models/HoverTank/tank_specular.png + Shininess : 8 + NormalMap : Models/HoverTank/tank_normals.png + DiffuseMap : Models/HoverTank/tank_diffuse.png + GlowMap : Models/HoverTank/tank_glow_map_highres.png + UseMaterialColors : true + Ambient : 0.0 0.0 0.0 1.0 + Diffuse : 1.0 1.0 1.0 1.0 + Specular : 1.0 1.0 1.0 1.0 + } +} diff --git a/engine/src/test-data/Models/HoverTank/tank_lowRes.j3m b/engine/src/test-data/Models/HoverTank/tank_lowRes.j3m new file mode 100644 index 000000000..3219159f6 --- /dev/null +++ b/engine/src/test-data/Models/HoverTank/tank_lowRes.j3m @@ -0,0 +1,9 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + SpecularMap : Models/HoverTank/tank_specular_lowRes.png + Shininess : 255 + NormalMap : Models/HoverTank/tank_normals_lowRes.png + DiffuseMap : Models/HoverTank/tank_diffuse_LowRes.png + GlowMap : Models/HoverTank/tank_glow_map.png + } +} diff --git a/engine/src/test-data/Models/HoverTank/tank_normals.png b/engine/src/test-data/Models/HoverTank/tank_normals.png new file mode 100644 index 000000000..130458838 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_normals.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_normals_lowRes.png b/engine/src/test-data/Models/HoverTank/tank_normals_lowRes.png new file mode 100644 index 000000000..794218793 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_normals_lowRes.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_specular.png b/engine/src/test-data/Models/HoverTank/tank_specular.png new file mode 100644 index 000000000..eb165ea09 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_specular.png differ diff --git a/engine/src/test-data/Models/HoverTank/tank_specular_lowRes.png b/engine/src/test-data/Models/HoverTank/tank_specular_lowRes.png new file mode 100644 index 000000000..c868ecac9 Binary files /dev/null and b/engine/src/test-data/Models/HoverTank/tank_specular_lowRes.png differ diff --git a/engine/src/test-data/Models/MonkeyHead/MonkeyHead.j3m b/engine/src/test-data/Models/MonkeyHead/MonkeyHead.j3m new file mode 100644 index 000000000..62568d094 --- /dev/null +++ b/engine/src/test-data/Models/MonkeyHead/MonkeyHead.j3m @@ -0,0 +1,8 @@ +Material Monkey Head : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess : 4.0 + DiffuseMap : Models/MonkeyHead/MonkeyHead_diffuse.jpg + NormalMap : Models/MonkeyHead/MonkeyHead_normal.jpg + SpecularMap : Models/MonkeyHead/MonkeyHead_spec.jpg + } +} \ No newline at end of file diff --git a/engine/src/test-data/Models/MonkeyHead/MonkeyHead.mesh.xml b/engine/src/test-data/Models/MonkeyHead/MonkeyHead.mesh.xml new file mode 100644 index 000000000..de1f88b54 --- /dev/null +++ b/engine/src/test-data/Models/MonkeyHead/MonkeyHead.mesh.xml @@ -0,0 +1,28388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/MonkeyHead/MonkeyHead_diffuse.jpg b/engine/src/test-data/Models/MonkeyHead/MonkeyHead_diffuse.jpg new file mode 100644 index 000000000..cfd825430 Binary files /dev/null and b/engine/src/test-data/Models/MonkeyHead/MonkeyHead_diffuse.jpg differ diff --git a/engine/src/test-data/Models/MonkeyHead/MonkeyHead_normal.jpg b/engine/src/test-data/Models/MonkeyHead/MonkeyHead_normal.jpg new file mode 100644 index 000000000..a36b2f986 Binary files /dev/null and b/engine/src/test-data/Models/MonkeyHead/MonkeyHead_normal.jpg differ diff --git a/engine/src/test-data/Models/MonkeyHead/MonkeyHead_spec.jpg b/engine/src/test-data/Models/MonkeyHead/MonkeyHead_spec.jpg new file mode 100644 index 000000000..0e1748887 Binary files /dev/null and b/engine/src/test-data/Models/MonkeyHead/MonkeyHead_spec.jpg differ diff --git a/engine/src/test-data/Models/Ninja/Ninja.jpg b/engine/src/test-data/Models/Ninja/Ninja.jpg new file mode 100644 index 000000000..9397f5d91 Binary files /dev/null and b/engine/src/test-data/Models/Ninja/Ninja.jpg differ diff --git a/engine/src/test-data/Models/Ninja/Ninja.material b/engine/src/test-data/Models/Ninja/Ninja.material new file mode 100644 index 000000000..71df902ef --- /dev/null +++ b/engine/src/test-data/Models/Ninja/Ninja.material @@ -0,0 +1,20 @@ +material Ninja +{ + technique + { + pass + { + ambient 0 0 0 1 + diffuse 1 1 1 1 + specular 1 1 1 25.6 + emissive 0 0 0 + + texture_unit + { + texture Ninja.jpg + tex_coord_set 0 + } + } + } +} + diff --git a/engine/src/test-data/Models/Ninja/Ninja.mesh.xml b/engine/src/test-data/Models/Ninja/Ninja.mesh.xml new file mode 100644 index 000000000..e51900827 --- /dev/null +++ b/engine/src/test-data/Models/Ninja/Ninja.mesh.xml @@ -0,0 +1,7773 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Ninja/Ninja.skeleton.xml b/engine/src/test-data/Models/Ninja/Ninja.skeleton.xml new file mode 100644 index 000000000..632539e31 --- /dev/null +++ b/engine/src/test-data/Models/Ninja/Ninja.skeleton.xml @@ -0,0 +1,13299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Oto/Oto.j3m b/engine/src/test-data/Models/Oto/Oto.j3m new file mode 100644 index 000000000..7ffd8660e --- /dev/null +++ b/engine/src/test-data/Models/Oto/Oto.j3m @@ -0,0 +1,6 @@ +Material OTO Lit : phong_lighting.j3md { + MaterialParameters { + Shininess: 8.0 + DiffuseMap: Models/Oto/Oto.jpg + } +} \ No newline at end of file diff --git a/engine/src/test-data/Models/Oto/Oto.jpg b/engine/src/test-data/Models/Oto/Oto.jpg new file mode 100644 index 000000000..2178a5e1e Binary files /dev/null and b/engine/src/test-data/Models/Oto/Oto.jpg differ diff --git a/engine/src/test-data/Models/Oto/Oto.material b/engine/src/test-data/Models/Oto/Oto.material new file mode 100644 index 000000000..b73faf413 --- /dev/null +++ b/engine/src/test-data/Models/Oto/Oto.material @@ -0,0 +1,15 @@ +material Material.002/SOLID/TEX/bluewarrior1024.j/VertCol +{ + technique + { + pass + { + diffuse vertexcolour + specular 0.000000 0.000000 0.000000 0.250000 + texture_unit + { + texture Oto.jpg + } + } + } +} diff --git a/engine/src/test-data/Models/Oto/Oto.mesh.xml b/engine/src/test-data/Models/Oto/Oto.mesh.xml new file mode 100644 index 000000000..a1c7f95b5 --- /dev/null +++ b/engine/src/test-data/Models/Oto/Oto.mesh.xml @@ -0,0 +1,29285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Oto/Oto.skeleton.xml b/engine/src/test-data/Models/Oto/Oto.skeleton.xml new file mode 100644 index 000000000..9e256af1a --- /dev/null +++ b/engine/src/test-data/Models/Oto/Oto.skeleton.xml @@ -0,0 +1,8801 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Sign Post/Sign Post.j3m b/engine/src/test-data/Models/Sign Post/Sign Post.j3m new file mode 100644 index 000000000..91967d5c4 --- /dev/null +++ b/engine/src/test-data/Models/Sign Post/Sign Post.j3m @@ -0,0 +1,12 @@ +Material Signpost : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 4.0 + DiffuseMap: Models/Sign Post/Sign Post.jpg + NormalMap: Models/Sign Post/Sign Post_normal.jpg + SpecularMap: Models/Sign Post/Sign Post_specular.jpg + UseMaterialColors : true + Ambient : 0.5 0.5 0.5 1.0 + Diffuse : 1.0 1.0 1.0 1.0 + Specular : 1.0 1.0 1.0 1.0 + } +} \ No newline at end of file diff --git a/engine/src/test-data/Models/Sign Post/Sign Post.jpg b/engine/src/test-data/Models/Sign Post/Sign Post.jpg new file mode 100644 index 000000000..193a0e64b Binary files /dev/null and b/engine/src/test-data/Models/Sign Post/Sign Post.jpg differ diff --git a/engine/src/test-data/Models/Sign Post/Sign Post.material b/engine/src/test-data/Models/Sign Post/Sign Post.material new file mode 100644 index 000000000..b4d3436df --- /dev/null +++ b/engine/src/test-data/Models/Sign Post/Sign Post.material @@ -0,0 +1,15 @@ +material Signpost/SOLID/TEX/signpost_color.jpg +{ + technique + { + pass + { + diffuse 1.000000 1.000000 1.000000 + specular 0.500000 0.500000 0.500000 12.500000 + texture_unit + { + texture signpost_color.jpg + } + } + } +} diff --git a/engine/src/test-data/Models/Sign Post/Sign Post.mesh.xml b/engine/src/test-data/Models/Sign Post/Sign Post.mesh.xml new file mode 100644 index 000000000..8fcd4334b --- /dev/null +++ b/engine/src/test-data/Models/Sign Post/Sign Post.mesh.xml @@ -0,0 +1,1547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Sign Post/Sign Post_normal.jpg b/engine/src/test-data/Models/Sign Post/Sign Post_normal.jpg new file mode 100644 index 000000000..bb8a0c893 Binary files /dev/null and b/engine/src/test-data/Models/Sign Post/Sign Post_normal.jpg differ diff --git a/engine/src/test-data/Models/Sign Post/Sign Post_specular.jpg b/engine/src/test-data/Models/Sign Post/Sign Post_specular.jpg new file mode 100644 index 000000000..67468106a Binary files /dev/null and b/engine/src/test-data/Models/Sign Post/Sign Post_specular.jpg differ diff --git a/engine/src/test-data/Models/Sinbad/README-LICENSE.txt b/engine/src/test-data/Models/Sinbad/README-LICENSE.txt new file mode 100644 index 000000000..7a0f73383 --- /dev/null +++ b/engine/src/test-data/Models/Sinbad/README-LICENSE.txt @@ -0,0 +1,23 @@ +----------------------------- +About: Sinbad Character Model +----------------------------- + +Artist: Zi Ye +Date: 2009-2010 +E-mail: omniter@gmail.com + +This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License. +To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a +letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. + +This character is a gift to the OGRE community (http://www.ogre3d.org). +You do not need to give credit to the artist, but it would be appreciated. =) + +This license applies to the following files: +- Sinbad.mesh +- Sinbad.skeleton +- Sinbad.blend +- sinbad_body.tga +- sinbad_clothes.tga +- sinbad_sword.tga +- Sword.mesh diff --git a/engine/src/test-data/Models/Sinbad/Sinbad.material b/engine/src/test-data/Models/Sinbad/Sinbad.material new file mode 100644 index 000000000..9ee737cdf --- /dev/null +++ b/engine/src/test-data/Models/Sinbad/Sinbad.material @@ -0,0 +1,125 @@ +material Sinbad/Body +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + + texture_unit + { + texture sinbad_body.jpg + } + } + } +} +material Sinbad/Gold +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 0.8 0.8 0.8 1 + specular 0.3 0.3 0.2 5.5 + + texture_unit + { + texture sinbad_clothes.jpg + } + } + } +} +material Sinbad/Sheaths +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.2 0.1 0.1 1.0 50.0 + + texture_unit + { + texture sinbad_sword.jpg + } + } + } +} +material Sinbad/Clothes +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.05 0.05 0.05 12.5 + + texture_unit + { + texture sinbad_clothes.jpg + } + } + } +} +material Sinbad/Teeth +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.25 0.25 0.25 10.5 + + texture_unit + { + texture sinbad_body.jpg + } + } + } +} +material Sinbad/Eyes +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.75 0.75 0.75 55.5 + + texture_unit + { + texture sinbad_body.jpg + } + } + } +} +material Sinbad/Spikes +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.75 0.75 0.75 20.5 + + texture_unit + { + texture sinbad_clothes.jpg + } + } + } +} diff --git a/engine/src/test-data/Models/Sinbad/Sinbad.mesh.xml b/engine/src/test-data/Models/Sinbad/Sinbad.mesh.xml new file mode 100644 index 000000000..91765a73c --- /dev/null +++ b/engine/src/test-data/Models/Sinbad/Sinbad.mesh.xml @@ -0,0 +1,55385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Sinbad/Sinbad.skeleton.xml b/engine/src/test-data/Models/Sinbad/Sinbad.skeleton.xml new file mode 100644 index 000000000..b6ba895bb --- /dev/null +++ b/engine/src/test-data/Models/Sinbad/Sinbad.skeleton.xml @@ -0,0 +1,58182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Sinbad/Sword.material b/engine/src/test-data/Models/Sinbad/Sword.material new file mode 100644 index 000000000..2baa89c18 --- /dev/null +++ b/engine/src/test-data/Models/Sinbad/Sword.material @@ -0,0 +1,72 @@ +material Sinbad/Blade +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 1 1 1 10.5 + + texture_unit + { + texture sinbad_sword.jpg + } + } + } +} +material Sinbad/Ruby +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.75 0.75 0.75 20.5 + + texture_unit + { + texture sinbad_sword.jpg + } + } + } +} +material Sinbad/Hilt +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 0.8 0.8 0.8 1 + specular 0.3 0.3 0.2 5.5 + + texture_unit + { + texture sinbad_sword.jpg + } + } + } +} +material Sinbad/Handle +{ + receive_shadows on + technique + { + pass + { + ambient 0.75 0.75 0.75 + diffuse 1 1 1 1 + specular 0.05 0.05 0.05 12.5 + + texture_unit + { + texture sinbad_sword.jpg + } + } + } +} diff --git a/engine/src/test-data/Models/Sinbad/Sword.mesh.xml b/engine/src/test-data/Models/Sinbad/Sword.mesh.xml new file mode 100644 index 000000000..eafa077b2 --- /dev/null +++ b/engine/src/test-data/Models/Sinbad/Sword.mesh.xml @@ -0,0 +1,2193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Sinbad/sinbad_body.jpg b/engine/src/test-data/Models/Sinbad/sinbad_body.jpg new file mode 100644 index 000000000..e9679d971 Binary files /dev/null and b/engine/src/test-data/Models/Sinbad/sinbad_body.jpg differ diff --git a/engine/src/test-data/Models/Sinbad/sinbad_clothes.jpg b/engine/src/test-data/Models/Sinbad/sinbad_clothes.jpg new file mode 100644 index 000000000..f52e014ca Binary files /dev/null and b/engine/src/test-data/Models/Sinbad/sinbad_clothes.jpg differ diff --git a/engine/src/test-data/Models/Sinbad/sinbad_sword.jpg b/engine/src/test-data/Models/Sinbad/sinbad_sword.jpg new file mode 100644 index 000000000..f555eabdc Binary files /dev/null and b/engine/src/test-data/Models/Sinbad/sinbad_sword.jpg differ diff --git a/engine/src/test-data/Models/SpaceCraft/Rocket.material b/engine/src/test-data/Models/SpaceCraft/Rocket.material new file mode 100644 index 000000000..9939857f2 --- /dev/null +++ b/engine/src/test-data/Models/SpaceCraft/Rocket.material @@ -0,0 +1,13 @@ +material SOLID/TEX/Rocket.tga +{ + technique + { + pass + { + texture_unit + { + texture Rocket.png + } + } + } +} diff --git a/engine/src/test-data/Models/SpaceCraft/Rocket.mesh.xml b/engine/src/test-data/Models/SpaceCraft/Rocket.mesh.xml new file mode 100644 index 000000000..14164245c --- /dev/null +++ b/engine/src/test-data/Models/SpaceCraft/Rocket.mesh.xml @@ -0,0 +1,1150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/SpaceCraft/Rocket.png b/engine/src/test-data/Models/SpaceCraft/Rocket.png new file mode 100644 index 000000000..ffb94aa4f Binary files /dev/null and b/engine/src/test-data/Models/SpaceCraft/Rocket.png differ diff --git a/engine/src/test-data/Models/Sponza/Sponza.j3o b/engine/src/test-data/Models/Sponza/Sponza.j3o new file mode 100644 index 000000000..ac71efa10 Binary files /dev/null and b/engine/src/test-data/Models/Sponza/Sponza.j3o differ diff --git a/engine/src/test-data/Models/Teapot/Teapot.mesh.xml b/engine/src/test-data/Models/Teapot/Teapot.mesh.xml new file mode 100644 index 000000000..b0aed3c42 --- /dev/null +++ b/engine/src/test-data/Models/Teapot/Teapot.mesh.xml @@ -0,0 +1,34297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Teapot/Teapot.mtl b/engine/src/test-data/Models/Teapot/Teapot.mtl new file mode 100644 index 000000000..11afe1167 --- /dev/null +++ b/engine/src/test-data/Models/Teapot/Teapot.mtl @@ -0,0 +1,12 @@ +# Blender3D MTL File: +# Material Count: 1 +newmtl m1 +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ni 1.000000 +d 1.000000 +illum 2 + + diff --git a/engine/src/test-data/Models/Teapot/Teapot.obj b/engine/src/test-data/Models/Teapot/Teapot.obj new file mode 100644 index 000000000..779b3b877 --- /dev/null +++ b/engine/src/test-data/Models/Teapot/Teapot.obj @@ -0,0 +1,15997 @@ +# Blender3D v248 OBJ File: +# www.blender3d.org +mtllib Teapot.mtl +v 0.350000 0.600000 -0.000000 +v 0.345492 0.600000 0.057428 +v 0.342019 0.608859 0.056851 +v 0.346481 0.608859 -0.000000 +v 0.340655 0.615750 0.056624 +v 0.345100 0.615750 -0.000000 +v 0.341105 0.620672 0.056699 +v 0.345556 0.620672 -0.000000 +v 0.343074 0.623625 0.057026 +v 0.347550 0.623625 -0.000000 +v 0.346263 0.624609 0.057556 +v 0.350781 0.624609 -0.000000 +v 0.350378 0.623625 0.058240 +v 0.354950 0.623625 -0.000000 +v 0.355123 0.620672 0.059029 +v 0.359756 0.620672 -0.000000 +v 0.360200 0.615750 0.059873 +v 0.364900 0.615750 -0.000000 +v 0.365315 0.608859 0.060723 +v 0.370081 0.608859 -0.000000 +v 0.370170 0.600000 0.061530 +v 0.375000 0.600000 -0.000000 +v 0.332416 0.600000 0.111664 +v 0.329074 0.608859 0.110541 +v 0.327762 0.615750 0.110101 +v 0.328196 0.620672 0.110246 +v 0.330089 0.623625 0.110882 +v 0.333158 0.624609 0.111913 +v 0.337117 0.623625 0.113243 +v 0.341682 0.620672 0.114777 +v 0.346567 0.615750 0.116418 +v 0.351488 0.608859 0.118071 +v 0.356160 0.600000 0.119640 +v 0.311444 0.600000 0.162036 +v 0.308313 0.608859 0.160407 +v 0.307084 0.615750 0.159767 +v 0.307490 0.620672 0.159979 +v 0.309264 0.623625 0.160902 +v 0.312139 0.624609 0.162398 +v 0.315849 0.623625 0.164328 +v 0.320125 0.620672 0.166553 +v 0.324703 0.615750 0.168934 +v 0.329313 0.608859 0.171333 +v 0.333690 0.600000 0.173610 +v 0.283248 0.600000 0.207872 +v 0.280400 0.608859 0.205782 +v 0.279283 0.615750 0.204962 +v 0.279652 0.620672 0.205233 +v 0.281265 0.623625 0.206417 +v 0.283880 0.624609 0.208336 +v 0.287254 0.623625 0.210812 +v 0.291144 0.620672 0.213666 +v 0.295306 0.615750 0.216721 +v 0.299499 0.608859 0.219799 +v 0.303480 0.600000 0.222720 +v 0.248500 0.600000 0.248500 +v 0.246002 0.608859 0.246002 +v 0.245021 0.615750 0.245021 +v 0.245345 0.620672 0.245345 +v 0.246761 0.623625 0.246760 +v 0.249055 0.624609 0.249055 +v 0.252014 0.623625 0.252014 +v 0.255427 0.620672 0.255427 +v 0.259079 0.615750 0.259079 +v 0.262758 0.608859 0.262758 +v 0.266250 0.600000 0.266250 +v 0.207872 0.600000 0.283248 +v 0.205782 0.608859 0.280400 +v 0.204962 0.615750 0.279283 +v 0.205233 0.620672 0.279652 +v 0.206417 0.623625 0.281265 +v 0.208336 0.624609 0.283880 +v 0.210812 0.623625 0.287254 +v 0.213666 0.620672 0.291144 +v 0.216721 0.615750 0.295306 +v 0.219799 0.608859 0.299499 +v 0.222720 0.600000 0.303480 +v 0.162036 0.600000 0.311444 +v 0.160407 0.608859 0.308313 +v 0.159767 0.615750 0.307084 +v 0.159979 0.620672 0.307490 +v 0.160902 0.623625 0.309264 +v 0.162398 0.624609 0.312139 +v 0.164328 0.623625 0.315849 +v 0.166553 0.620672 0.320126 +v 0.168934 0.615750 0.324703 +v 0.171333 0.608859 0.329313 +v 0.173610 0.600000 0.333690 +v 0.111664 0.600000 0.332416 +v 0.110541 0.608859 0.329074 +v 0.110101 0.615750 0.327762 +v 0.110246 0.620672 0.328196 +v 0.110882 0.623625 0.330089 +v 0.111913 0.624609 0.333158 +v 0.113243 0.623625 0.337117 +v 0.114777 0.620672 0.341682 +v 0.116418 0.615750 0.346567 +v 0.118071 0.608859 0.351488 +v 0.119640 0.600000 0.356160 +v 0.057428 0.600000 0.345492 +v 0.056851 0.608859 0.342019 +v 0.056624 0.615750 0.340655 +v 0.056699 0.620672 0.341105 +v 0.057026 0.623625 0.343074 +v 0.057556 0.624609 0.346263 +v 0.058240 0.623625 0.350378 +v 0.059029 0.620672 0.355123 +v 0.059873 0.615750 0.360200 +v 0.060723 0.608859 0.365315 +v 0.061530 0.600000 0.370170 +v -0.000000 0.600000 0.350000 +v -0.000000 0.608859 0.346481 +v -0.000000 0.615750 0.345100 +v -0.000000 0.620672 0.345556 +v -0.000000 0.623625 0.347550 +v -0.000000 0.624609 0.350781 +v -0.000000 0.623625 0.354950 +v -0.000000 0.620672 0.359756 +v -0.000000 0.615750 0.364900 +v -0.000000 0.608859 0.370081 +v -0.000000 0.600000 0.375000 +v -0.062895 0.600000 0.345492 +v -0.060836 0.608859 0.342019 +v -0.059423 0.615750 0.340655 +v -0.058574 0.620672 0.341105 +v -0.058207 0.623625 0.343074 +v -0.058240 0.624609 0.346263 +v -0.058590 0.623625 0.350378 +v -0.059176 0.620672 0.355123 +v -0.059917 0.615750 0.360200 +v -0.060728 0.608859 0.365315 +v -0.061530 0.600000 0.370170 +v -0.120304 0.600000 0.332416 +v -0.116840 0.608859 0.329074 +v -0.114524 0.615750 0.327762 +v -0.113210 0.620672 0.328196 +v -0.112749 0.623625 0.330089 +v -0.112993 0.624609 0.333158 +v -0.113796 0.623625 0.337117 +v -0.115010 0.620672 0.341682 +v -0.116487 0.615750 0.346567 +v -0.118079 0.608859 0.351488 +v -0.119640 0.600000 0.356160 +v -0.171959 0.600000 0.311444 +v -0.167640 0.608859 0.308313 +v -0.164848 0.615750 0.307084 +v -0.163382 0.620672 0.307490 +v -0.163045 0.623625 0.309264 +v -0.163638 0.624609 0.312139 +v -0.164963 0.623625 0.315849 +v -0.166821 0.620672 0.320125 +v -0.169014 0.615750 0.324703 +v -0.171343 0.608859 0.329313 +v -0.173610 0.600000 0.333690 +v -0.217592 0.600000 0.283248 +v -0.212868 0.608859 0.280400 +v -0.209938 0.615750 0.279283 +v -0.208567 0.620672 0.279652 +v -0.208516 0.623625 0.281265 +v -0.209551 0.624609 0.283880 +v -0.211434 0.623625 0.287254 +v -0.213929 0.620672 0.291144 +v -0.216799 0.615750 0.295306 +v -0.219808 0.608859 0.299499 +v -0.222720 0.600000 0.303480 +v -0.256938 0.600000 0.248500 +v -0.252153 0.608859 0.246002 +v -0.249341 0.615750 0.245021 +v -0.248239 0.620672 0.245345 +v -0.248583 0.623625 0.246760 +v -0.250109 0.624609 0.249055 +v -0.252555 0.623625 0.252014 +v -0.255655 0.620672 0.255427 +v -0.259146 0.615750 0.259079 +v -0.262766 0.608859 0.262758 +v -0.266250 0.600000 0.266250 +v -0.289728 0.600000 0.207872 +v -0.285124 0.608859 0.205782 +v -0.282600 0.615750 0.204962 +v -0.281874 0.620672 0.205233 +v -0.282665 0.623625 0.206417 +v -0.284690 0.624609 0.208336 +v -0.287669 0.623625 0.210812 +v -0.291319 0.620672 0.213666 +v -0.295358 0.615750 0.216721 +v -0.299506 0.608859 0.219799 +v -0.303480 0.600000 0.222720 +v -0.315697 0.600000 0.162036 +v -0.311413 0.608859 0.160407 +v -0.309261 0.615750 0.159767 +v -0.308948 0.620672 0.159979 +v -0.310182 0.623625 0.160902 +v -0.312671 0.624609 0.162398 +v -0.316121 0.623625 0.164328 +v -0.320240 0.620672 0.166553 +v -0.324737 0.615750 0.168934 +v -0.329317 0.608859 0.171333 +v -0.333690 0.600000 0.173610 +v -0.334576 0.600000 0.111664 +v -0.330649 0.608859 0.110541 +v -0.328868 0.615750 0.110101 +v -0.328936 0.620672 0.110246 +v -0.330556 0.623625 0.110882 +v -0.333428 0.624609 0.111913 +v -0.337256 0.623625 0.113243 +v -0.341740 0.620672 0.114777 +v -0.346585 0.615750 0.116418 +v -0.351491 0.608859 0.118071 +v -0.356160 0.600000 0.119640 +v -0.346099 0.600000 0.057428 +v -0.342461 0.608859 0.056851 +v -0.340966 0.615750 0.056624 +v -0.341314 0.620672 0.056699 +v -0.343205 0.623625 0.057026 +v -0.346339 0.624609 0.057556 +v -0.350417 0.623625 0.058240 +v -0.355139 0.620672 0.059029 +v -0.360205 0.615750 0.059873 +v -0.365315 0.608859 0.060723 +v -0.370170 0.600000 0.061530 +v -0.350000 0.600000 -0.000000 +v -0.346481 0.608859 -0.000000 +v -0.345100 0.615750 -0.000000 +v -0.345556 0.620672 -0.000000 +v -0.347550 0.623625 -0.000000 +v -0.350781 0.624609 -0.000000 +v -0.354950 0.623625 -0.000000 +v -0.359756 0.620672 -0.000000 +v -0.364900 0.615750 -0.000000 +v -0.370081 0.608859 -0.000000 +v -0.375000 0.600000 -0.000000 +v -0.345492 0.600000 -0.057428 +v -0.342019 0.608859 -0.056851 +v -0.340655 0.615750 -0.056624 +v -0.341105 0.620672 -0.056699 +v -0.343074 0.623625 -0.057026 +v -0.346263 0.624609 -0.057556 +v -0.350378 0.623625 -0.058240 +v -0.355123 0.620672 -0.059029 +v -0.360200 0.615750 -0.059873 +v -0.365315 0.608859 -0.060723 +v -0.370170 0.600000 -0.061530 +v -0.332416 0.600000 -0.111664 +v -0.329074 0.608859 -0.110541 +v -0.327762 0.615750 -0.110101 +v -0.328196 0.620672 -0.110246 +v -0.330089 0.623625 -0.110882 +v -0.333158 0.624609 -0.111913 +v -0.337117 0.623625 -0.113243 +v -0.341682 0.620672 -0.114777 +v -0.346567 0.615750 -0.116418 +v -0.351488 0.608859 -0.118071 +v -0.356160 0.600000 -0.119640 +v -0.311444 0.600000 -0.162036 +v -0.308313 0.608859 -0.160407 +v -0.307084 0.615750 -0.159768 +v -0.307490 0.620672 -0.159979 +v -0.309264 0.623625 -0.160902 +v -0.312139 0.624609 -0.162398 +v -0.315849 0.623625 -0.164328 +v -0.320125 0.620672 -0.166553 +v -0.324703 0.615750 -0.168934 +v -0.329313 0.608859 -0.171333 +v -0.333690 0.600000 -0.173610 +v -0.283248 0.600000 -0.207872 +v -0.280400 0.608859 -0.205782 +v -0.279283 0.615750 -0.204962 +v -0.279652 0.620672 -0.205233 +v -0.281265 0.623625 -0.206417 +v -0.283880 0.624609 -0.208336 +v -0.287254 0.623625 -0.210812 +v -0.291144 0.620672 -0.213666 +v -0.295306 0.615750 -0.216721 +v -0.299499 0.608859 -0.219799 +v -0.303480 0.600000 -0.222720 +v -0.248500 0.600000 -0.248500 +v -0.246002 0.608859 -0.246002 +v -0.245021 0.615750 -0.245021 +v -0.245345 0.620672 -0.245345 +v -0.246761 0.623625 -0.246761 +v -0.249055 0.624609 -0.249055 +v -0.252014 0.623625 -0.252015 +v -0.255427 0.620672 -0.255427 +v -0.259079 0.615750 -0.259079 +v -0.262758 0.608859 -0.262758 +v -0.266250 0.600000 -0.266250 +v -0.207872 0.600000 -0.283248 +v -0.205782 0.608859 -0.280400 +v -0.204962 0.615750 -0.279283 +v -0.205233 0.620672 -0.279652 +v -0.206417 0.623625 -0.281265 +v -0.208336 0.624609 -0.283880 +v -0.210812 0.623625 -0.287254 +v -0.213666 0.620672 -0.291144 +v -0.216721 0.615750 -0.295306 +v -0.219799 0.608859 -0.299499 +v -0.222720 0.600000 -0.303480 +v -0.162036 0.600000 -0.311444 +v -0.160407 0.608859 -0.308313 +v -0.159767 0.615750 -0.307084 +v -0.159979 0.620672 -0.307490 +v -0.160902 0.623625 -0.309264 +v -0.162398 0.624609 -0.312139 +v -0.164328 0.623625 -0.315849 +v -0.166553 0.620672 -0.320126 +v -0.168934 0.615750 -0.324703 +v -0.171333 0.608859 -0.329313 +v -0.173610 0.600000 -0.333690 +v -0.111664 0.600000 -0.332416 +v -0.110541 0.608859 -0.329074 +v -0.110101 0.615750 -0.327762 +v -0.110246 0.620672 -0.328196 +v -0.110882 0.623625 -0.330089 +v -0.111913 0.624609 -0.333158 +v -0.113243 0.623625 -0.337117 +v -0.114777 0.620672 -0.341682 +v -0.116418 0.615750 -0.346567 +v -0.118071 0.608859 -0.351488 +v -0.119640 0.600000 -0.356160 +v -0.057428 0.600000 -0.345492 +v -0.056851 0.608859 -0.342019 +v -0.056624 0.615750 -0.340655 +v -0.056699 0.620672 -0.341106 +v -0.057026 0.623625 -0.343074 +v -0.057556 0.624609 -0.346263 +v -0.058240 0.623625 -0.350378 +v -0.059029 0.620672 -0.355123 +v -0.059873 0.615750 -0.360200 +v -0.060723 0.608859 -0.365315 +v -0.061530 0.600000 -0.370170 +v 0.000000 0.600000 -0.350000 +v 0.000000 0.608859 -0.346481 +v 0.000000 0.615750 -0.345100 +v 0.000000 0.620672 -0.345556 +v 0.000000 0.623625 -0.347550 +v 0.000000 0.624609 -0.350781 +v 0.000000 0.623625 -0.354950 +v 0.000000 0.620672 -0.359756 +v 0.000000 0.615750 -0.364900 +v 0.000000 0.608859 -0.370081 +v 0.000000 0.600000 -0.375000 +v 0.057428 0.600000 -0.345492 +v 0.056851 0.608859 -0.342019 +v 0.056624 0.615750 -0.340655 +v 0.056699 0.620672 -0.341106 +v 0.057026 0.623625 -0.343074 +v 0.057556 0.624609 -0.346263 +v 0.058240 0.623625 -0.350378 +v 0.059029 0.620672 -0.355123 +v 0.059873 0.615750 -0.360200 +v 0.060723 0.608859 -0.365315 +v 0.061530 0.600000 -0.370170 +v 0.111664 0.600000 -0.332416 +v 0.110541 0.608859 -0.329074 +v 0.110101 0.615750 -0.327762 +v 0.110246 0.620672 -0.328196 +v 0.110882 0.623625 -0.330089 +v 0.111913 0.624609 -0.333158 +v 0.113243 0.623625 -0.337117 +v 0.114777 0.620672 -0.341682 +v 0.116418 0.615750 -0.346567 +v 0.118071 0.608859 -0.351488 +v 0.119640 0.600000 -0.356160 +v 0.162036 0.600000 -0.311444 +v 0.160407 0.608859 -0.308313 +v 0.159768 0.615750 -0.307084 +v 0.159979 0.620672 -0.307490 +v 0.160902 0.623625 -0.309264 +v 0.162398 0.624609 -0.312139 +v 0.164328 0.623625 -0.315849 +v 0.166553 0.620672 -0.320126 +v 0.168934 0.615750 -0.324703 +v 0.171333 0.608859 -0.329313 +v 0.173610 0.600000 -0.333690 +v 0.207872 0.600000 -0.283248 +v 0.205782 0.608859 -0.280400 +v 0.204962 0.615750 -0.279283 +v 0.205233 0.620672 -0.279652 +v 0.206417 0.623625 -0.281265 +v 0.208336 0.624609 -0.283880 +v 0.210812 0.623625 -0.287254 +v 0.213666 0.620672 -0.291144 +v 0.216721 0.615750 -0.295306 +v 0.219799 0.608859 -0.299499 +v 0.222720 0.600000 -0.303480 +v 0.248500 0.600000 -0.248500 +v 0.246002 0.608859 -0.246002 +v 0.245021 0.615750 -0.245021 +v 0.245345 0.620672 -0.245345 +v 0.246761 0.623625 -0.246761 +v 0.249055 0.624609 -0.249055 +v 0.252014 0.623625 -0.252015 +v 0.255427 0.620672 -0.255427 +v 0.259079 0.615750 -0.259079 +v 0.262758 0.608859 -0.262758 +v 0.266250 0.600000 -0.266250 +v 0.283248 0.600000 -0.207872 +v 0.280400 0.608859 -0.205782 +v 0.279283 0.615750 -0.204962 +v 0.279652 0.620672 -0.205233 +v 0.281265 0.623625 -0.206417 +v 0.283880 0.624609 -0.208336 +v 0.287254 0.623625 -0.210812 +v 0.291144 0.620672 -0.213666 +v 0.295306 0.615750 -0.216721 +v 0.299499 0.608859 -0.219799 +v 0.303480 0.600000 -0.222720 +v 0.311444 0.600000 -0.162036 +v 0.308313 0.608859 -0.160407 +v 0.307084 0.615750 -0.159768 +v 0.307490 0.620672 -0.159979 +v 0.309264 0.623625 -0.160902 +v 0.312139 0.624609 -0.162398 +v 0.315849 0.623625 -0.164328 +v 0.320126 0.620672 -0.166553 +v 0.324703 0.615750 -0.168934 +v 0.329313 0.608859 -0.171333 +v 0.333690 0.600000 -0.173610 +v 0.332416 0.600000 -0.111664 +v 0.329074 0.608859 -0.110541 +v 0.327762 0.615750 -0.110101 +v 0.328196 0.620672 -0.110246 +v 0.330089 0.623625 -0.110882 +v 0.333158 0.624609 -0.111913 +v 0.337117 0.623625 -0.113243 +v 0.341682 0.620672 -0.114777 +v 0.346567 0.615750 -0.116418 +v 0.351488 0.608859 -0.118071 +v 0.356160 0.600000 -0.119640 +v 0.345492 0.600000 -0.057428 +v 0.342019 0.608859 -0.056851 +v 0.340655 0.615750 -0.056624 +v 0.341105 0.620672 -0.056699 +v 0.343074 0.623625 -0.057026 +v 0.346263 0.624609 -0.057556 +v 0.350378 0.623625 -0.058240 +v 0.355123 0.620672 -0.059029 +v 0.360200 0.615750 -0.059873 +v 0.365315 0.608859 -0.060723 +v 0.370170 0.600000 -0.061530 +v 0.388617 0.560644 0.064596 +v 0.393687 0.560644 -0.000000 +v 0.406693 0.521400 0.067601 +v 0.412000 0.521400 -0.000000 +v 0.424030 0.482381 0.070483 +v 0.429563 0.482381 -0.000000 +v 0.440256 0.443700 0.073180 +v 0.446000 0.443700 -0.000000 +v 0.455001 0.405469 0.075631 +v 0.460938 0.405469 -0.000000 +v 0.467895 0.367800 0.077774 +v 0.474000 0.367800 -0.000000 +v 0.478568 0.330806 0.079548 +v 0.484812 0.330806 -0.000000 +v 0.486650 0.294600 0.080891 +v 0.493000 0.294600 -0.000000 +v 0.491771 0.259294 0.081743 +v 0.498187 0.259294 -0.000000 +v 0.493560 0.225000 0.082040 +v 0.500000 0.225000 -0.000000 +v 0.373909 0.560644 0.125602 +v 0.391301 0.521400 0.131444 +v 0.407981 0.482381 0.137048 +v 0.423593 0.443700 0.142292 +v 0.437780 0.405469 0.147057 +v 0.450186 0.367800 0.151225 +v 0.460456 0.330806 0.154675 +v 0.468232 0.294600 0.157287 +v 0.473159 0.259294 0.158942 +v 0.474880 0.225000 0.159520 +v 0.350319 0.560644 0.182262 +v 0.366614 0.521400 0.190740 +v 0.382242 0.482381 0.198870 +v 0.396869 0.443700 0.206480 +v 0.410161 0.405469 0.213396 +v 0.421784 0.367800 0.219443 +v 0.431406 0.330806 0.224449 +v 0.438691 0.294600 0.228239 +v 0.443307 0.259294 0.230641 +v 0.444920 0.225000 0.231480 +v 0.318603 0.560644 0.233819 +v 0.333423 0.521400 0.244695 +v 0.347636 0.482381 0.255126 +v 0.360939 0.443700 0.264888 +v 0.373028 0.405469 0.273760 +v 0.383599 0.367800 0.281518 +v 0.392349 0.330806 0.287940 +v 0.398975 0.294600 0.292803 +v 0.403173 0.259294 0.295884 +v 0.404640 0.225000 0.296960 +v 0.279518 0.560644 0.279518 +v 0.292520 0.521400 0.292520 +v 0.304989 0.482381 0.304989 +v 0.316660 0.443700 0.316660 +v 0.327266 0.405469 0.327266 +v 0.336540 0.367800 0.336540 +v 0.344217 0.330806 0.344217 +v 0.350030 0.294600 0.350030 +v 0.353713 0.259294 0.353713 +v 0.355000 0.225000 0.355000 +v 0.233819 0.560644 0.318603 +v 0.244695 0.521400 0.333423 +v 0.255126 0.482381 0.347636 +v 0.264888 0.443700 0.360939 +v 0.273760 0.405469 0.373027 +v 0.281518 0.367800 0.383599 +v 0.287940 0.330806 0.392349 +v 0.292803 0.294600 0.398975 +v 0.295884 0.259294 0.403173 +v 0.296960 0.225000 0.404640 +v 0.182262 0.560644 0.350319 +v 0.190740 0.521400 0.366614 +v 0.198870 0.482381 0.382242 +v 0.206480 0.443700 0.396869 +v 0.213396 0.405469 0.410161 +v 0.219443 0.367800 0.421784 +v 0.224449 0.330806 0.431406 +v 0.228239 0.294600 0.438691 +v 0.230641 0.259294 0.443307 +v 0.231480 0.225000 0.444920 +v 0.125602 0.560644 0.373909 +v 0.131444 0.521400 0.391301 +v 0.137048 0.482381 0.407981 +v 0.142292 0.443700 0.423593 +v 0.147057 0.405469 0.437780 +v 0.151225 0.367800 0.450186 +v 0.154675 0.330806 0.460456 +v 0.157287 0.294600 0.468232 +v 0.158942 0.259294 0.473159 +v 0.159520 0.225000 0.474880 +v 0.064596 0.560644 0.388617 +v 0.067601 0.521400 0.406693 +v 0.070483 0.482381 0.424030 +v 0.073180 0.443700 0.440256 +v 0.075631 0.405469 0.455001 +v 0.077774 0.367800 0.467895 +v 0.079548 0.330806 0.478568 +v 0.080891 0.294600 0.486650 +v 0.081743 0.259294 0.491771 +v 0.082040 0.225000 0.493560 +v -0.000000 0.560644 0.393687 +v -0.000000 0.521400 0.412000 +v -0.000000 0.482381 0.429563 +v -0.000000 0.443700 0.446000 +v -0.000000 0.405469 0.460938 +v -0.000000 0.367800 0.474000 +v -0.000000 0.330806 0.484812 +v -0.000000 0.294600 0.493000 +v -0.000000 0.259294 0.498187 +v -0.000000 0.225000 0.500000 +v -0.064596 0.560644 0.388617 +v -0.067601 0.521400 0.406693 +v -0.070483 0.482381 0.424030 +v -0.073180 0.443700 0.440256 +v -0.075631 0.405469 0.455001 +v -0.077774 0.367800 0.467895 +v -0.079548 0.330806 0.478568 +v -0.080891 0.294600 0.486650 +v -0.081743 0.259294 0.491771 +v -0.082040 0.225000 0.493560 +v -0.125602 0.560644 0.373909 +v -0.131444 0.521400 0.391301 +v -0.137048 0.482381 0.407981 +v -0.142292 0.443700 0.423593 +v -0.147058 0.405469 0.437780 +v -0.151225 0.367800 0.450186 +v -0.154675 0.330806 0.460456 +v -0.157287 0.294600 0.468232 +v -0.158942 0.259294 0.473159 +v -0.159520 0.225000 0.474880 +v -0.182262 0.560644 0.350319 +v -0.190740 0.521400 0.366614 +v -0.198870 0.482381 0.382242 +v -0.206480 0.443700 0.396869 +v -0.213396 0.405469 0.410161 +v -0.219443 0.367800 0.421784 +v -0.224449 0.330806 0.431406 +v -0.228239 0.294600 0.438691 +v -0.230641 0.259294 0.443307 +v -0.231480 0.225000 0.444920 +v -0.233819 0.560644 0.318603 +v -0.244695 0.521400 0.333423 +v -0.255126 0.482381 0.347636 +v -0.264888 0.443700 0.360939 +v -0.273760 0.405469 0.373027 +v -0.281518 0.367800 0.383599 +v -0.287940 0.330806 0.392349 +v -0.292803 0.294600 0.398975 +v -0.295884 0.259294 0.403173 +v -0.296960 0.225000 0.404640 +v -0.279518 0.560644 0.279518 +v -0.292520 0.521400 0.292520 +v -0.304989 0.482381 0.304989 +v -0.316660 0.443700 0.316660 +v -0.327266 0.405469 0.327266 +v -0.336540 0.367800 0.336540 +v -0.344217 0.330806 0.344217 +v -0.350030 0.294600 0.350030 +v -0.353713 0.259294 0.353713 +v -0.355000 0.225000 0.355000 +v -0.318603 0.560644 0.233819 +v -0.333423 0.521400 0.244695 +v -0.347636 0.482381 0.255126 +v -0.360939 0.443700 0.264888 +v -0.373028 0.405469 0.273760 +v -0.383599 0.367800 0.281518 +v -0.392349 0.330806 0.287940 +v -0.398975 0.294600 0.292803 +v -0.403173 0.259294 0.295884 +v -0.404640 0.225000 0.296960 +v -0.350319 0.560644 0.182262 +v -0.366614 0.521400 0.190739 +v -0.382242 0.482381 0.198870 +v -0.396869 0.443700 0.206480 +v -0.410161 0.405469 0.213396 +v -0.421784 0.367800 0.219443 +v -0.431406 0.330806 0.224449 +v -0.438691 0.294600 0.228239 +v -0.443307 0.259294 0.230641 +v -0.444920 0.225000 0.231480 +v -0.373909 0.560644 0.125602 +v -0.391301 0.521400 0.131444 +v -0.407981 0.482381 0.137048 +v -0.423593 0.443700 0.142292 +v -0.437780 0.405469 0.147057 +v -0.450186 0.367800 0.151225 +v -0.460456 0.330806 0.154675 +v -0.468232 0.294600 0.157287 +v -0.473159 0.259294 0.158942 +v -0.474880 0.225000 0.159520 +v -0.388617 0.560644 0.064596 +v -0.406693 0.521400 0.067601 +v -0.424030 0.482381 0.070482 +v -0.440256 0.443700 0.073180 +v -0.455001 0.405469 0.075630 +v -0.467895 0.367800 0.077774 +v -0.478568 0.330806 0.079548 +v -0.486650 0.294600 0.080891 +v -0.491771 0.259294 0.081742 +v -0.493560 0.225000 0.082040 +v -0.393687 0.560644 -0.000000 +v -0.412000 0.521400 -0.000000 +v -0.429563 0.482381 -0.000000 +v -0.446000 0.443700 -0.000000 +v -0.460938 0.405469 -0.000000 +v -0.474000 0.367800 -0.000000 +v -0.484812 0.330806 -0.000000 +v -0.493000 0.294600 -0.000000 +v -0.498187 0.259294 -0.000000 +v -0.388617 0.560644 -0.064596 +v -0.406693 0.521400 -0.067601 +v -0.424030 0.482381 -0.070483 +v -0.440256 0.443700 -0.073180 +v -0.455001 0.405469 -0.075631 +v -0.467895 0.367800 -0.077774 +v -0.478568 0.330806 -0.079548 +v -0.486650 0.294600 -0.080892 +v -0.491771 0.259294 -0.081743 +v -0.493560 0.225000 -0.082040 +v -0.373909 0.560644 -0.125602 +v -0.391301 0.521400 -0.131445 +v -0.407981 0.482381 -0.137048 +v -0.423593 0.443700 -0.142292 +v -0.437780 0.405469 -0.147058 +v -0.450186 0.367800 -0.151225 +v -0.460456 0.330806 -0.154675 +v -0.468232 0.294600 -0.157287 +v -0.473159 0.259294 -0.158942 +v -0.474880 0.225000 -0.159520 +v -0.350319 0.560644 -0.182262 +v -0.366614 0.521400 -0.190740 +v -0.382242 0.482381 -0.198870 +v -0.396869 0.443700 -0.206480 +v -0.410161 0.405469 -0.213396 +v -0.421784 0.367800 -0.219443 +v -0.431406 0.330806 -0.224449 +v -0.438691 0.294600 -0.228239 +v -0.443307 0.259294 -0.230641 +v -0.444920 0.225000 -0.231480 +v -0.318603 0.560644 -0.233819 +v -0.333423 0.521400 -0.244695 +v -0.347636 0.482381 -0.255126 +v -0.360939 0.443700 -0.264888 +v -0.373028 0.405469 -0.273760 +v -0.383599 0.367800 -0.281518 +v -0.392349 0.330806 -0.287940 +v -0.398975 0.294600 -0.292803 +v -0.403173 0.259294 -0.295884 +v -0.404640 0.225000 -0.296960 +v -0.279518 0.560644 -0.279518 +v -0.292520 0.521400 -0.292520 +v -0.304989 0.482381 -0.304989 +v -0.316660 0.443700 -0.316660 +v -0.327266 0.405469 -0.327266 +v -0.336540 0.367800 -0.336540 +v -0.344217 0.330806 -0.344217 +v -0.350030 0.294600 -0.350030 +v -0.353713 0.259294 -0.353713 +v -0.355000 0.225000 -0.355000 +v -0.233819 0.560644 -0.318603 +v -0.244695 0.521400 -0.333423 +v -0.255126 0.482381 -0.347636 +v -0.264888 0.443700 -0.360939 +v -0.273760 0.405469 -0.373028 +v -0.281518 0.367800 -0.383599 +v -0.287940 0.330806 -0.392349 +v -0.292803 0.294600 -0.398975 +v -0.295884 0.259294 -0.403173 +v -0.296960 0.225000 -0.404640 +v -0.182262 0.560644 -0.350319 +v -0.190740 0.521400 -0.366614 +v -0.198870 0.482381 -0.382242 +v -0.206480 0.443700 -0.396869 +v -0.213396 0.405469 -0.410161 +v -0.219443 0.367800 -0.421784 +v -0.224449 0.330806 -0.431406 +v -0.228239 0.294600 -0.438691 +v -0.230641 0.259294 -0.443307 +v -0.231480 0.225000 -0.444920 +v -0.125602 0.560644 -0.373909 +v -0.131444 0.521400 -0.391301 +v -0.137048 0.482381 -0.407981 +v -0.142292 0.443700 -0.423593 +v -0.147057 0.405469 -0.437780 +v -0.151225 0.367800 -0.450186 +v -0.154675 0.330806 -0.460456 +v -0.157287 0.294600 -0.468232 +v -0.158942 0.259294 -0.473159 +v -0.159520 0.225000 -0.474880 +v -0.064596 0.560644 -0.388617 +v -0.067601 0.521400 -0.406693 +v -0.070483 0.482381 -0.424030 +v -0.073180 0.443700 -0.440256 +v -0.075631 0.405469 -0.455001 +v -0.077774 0.367800 -0.467895 +v -0.079548 0.330806 -0.478568 +v -0.080891 0.294600 -0.486650 +v -0.081743 0.259294 -0.491771 +v -0.082040 0.225000 -0.493560 +v 0.000000 0.560644 -0.393688 +v 0.000000 0.521400 -0.412000 +v 0.000000 0.482381 -0.429563 +v 0.000000 0.443700 -0.446000 +v 0.000000 0.405469 -0.460938 +v 0.000000 0.367800 -0.474000 +v 0.000000 0.330806 -0.484812 +v 0.000000 0.294600 -0.493000 +v 0.000000 0.259294 -0.498187 +v 0.000000 0.225000 -0.500000 +v 0.064596 0.560644 -0.388617 +v 0.067601 0.521400 -0.406693 +v 0.070483 0.482381 -0.424030 +v 0.073180 0.443700 -0.440256 +v 0.075631 0.405469 -0.455001 +v 0.077774 0.367800 -0.467895 +v 0.079548 0.330806 -0.478568 +v 0.080891 0.294600 -0.486650 +v 0.081743 0.259294 -0.491771 +v 0.082040 0.225000 -0.493560 +v 0.125602 0.560644 -0.373909 +v 0.131444 0.521400 -0.391301 +v 0.137048 0.482381 -0.407981 +v 0.142292 0.443700 -0.423593 +v 0.147058 0.405469 -0.437780 +v 0.151225 0.367800 -0.450186 +v 0.154675 0.330806 -0.460456 +v 0.157287 0.294600 -0.468232 +v 0.158942 0.259294 -0.473159 +v 0.159520 0.225000 -0.474880 +v 0.182262 0.560644 -0.350319 +v 0.190740 0.521400 -0.366614 +v 0.198870 0.482381 -0.382242 +v 0.206480 0.443700 -0.396869 +v 0.213396 0.405469 -0.410161 +v 0.219443 0.367800 -0.421784 +v 0.224449 0.330806 -0.431406 +v 0.228239 0.294600 -0.438691 +v 0.230641 0.259294 -0.443307 +v 0.231480 0.225000 -0.444920 +v 0.233819 0.560644 -0.318603 +v 0.244695 0.521400 -0.333423 +v 0.255126 0.482381 -0.347636 +v 0.264888 0.443700 -0.360939 +v 0.273760 0.405469 -0.373028 +v 0.281518 0.367800 -0.383599 +v 0.287940 0.330806 -0.392349 +v 0.292803 0.294600 -0.398975 +v 0.295884 0.259294 -0.403173 +v 0.296960 0.225000 -0.404640 +v 0.279518 0.560644 -0.279518 +v 0.292520 0.521400 -0.292520 +v 0.304989 0.482381 -0.304989 +v 0.316660 0.443700 -0.316660 +v 0.327266 0.405469 -0.327266 +v 0.336540 0.367800 -0.336540 +v 0.344217 0.330806 -0.344217 +v 0.350030 0.294600 -0.350030 +v 0.353713 0.259294 -0.353713 +v 0.355000 0.225000 -0.355000 +v 0.318603 0.560644 -0.233819 +v 0.333423 0.521400 -0.244695 +v 0.347636 0.482381 -0.255126 +v 0.360939 0.443700 -0.264888 +v 0.373028 0.405469 -0.273760 +v 0.383599 0.367800 -0.281518 +v 0.392349 0.330806 -0.287940 +v 0.398975 0.294600 -0.292803 +v 0.403173 0.259294 -0.295884 +v 0.404640 0.225000 -0.296960 +v 0.350319 0.560644 -0.182262 +v 0.366614 0.521400 -0.190740 +v 0.382242 0.482381 -0.198870 +v 0.396869 0.443700 -0.206480 +v 0.410161 0.405469 -0.213396 +v 0.421784 0.367800 -0.219443 +v 0.431406 0.330806 -0.224449 +v 0.438691 0.294600 -0.228239 +v 0.443307 0.259294 -0.230641 +v 0.444920 0.225000 -0.231480 +v 0.373909 0.560644 -0.125602 +v 0.391301 0.521400 -0.131444 +v 0.407981 0.482381 -0.137048 +v 0.423593 0.443700 -0.142292 +v 0.437780 0.405469 -0.147057 +v 0.450186 0.367800 -0.151225 +v 0.460456 0.330806 -0.154675 +v 0.468232 0.294600 -0.157287 +v 0.473159 0.259294 -0.158942 +v 0.474880 0.225000 -0.159520 +v 0.388617 0.560644 -0.064596 +v 0.406693 0.521400 -0.067601 +v 0.424030 0.482381 -0.070483 +v 0.440256 0.443700 -0.073180 +v 0.455001 0.405469 -0.075631 +v 0.467895 0.367800 -0.077774 +v 0.478568 0.330806 -0.079548 +v 0.486650 0.294600 -0.080891 +v 0.491771 0.259294 -0.081743 +v 0.493560 0.225000 -0.082040 +v 0.490105 0.192919 0.081466 +v 0.496500 0.192919 -0.000000 +v 0.480727 0.164100 0.079907 +v 0.487000 0.164100 -0.000000 +v 0.466908 0.138431 0.077610 +v 0.473000 0.138431 -0.000000 +v 0.450127 0.115800 0.074820 +v 0.456000 0.115800 -0.000000 +v 0.431865 0.096094 0.071785 +v 0.437500 0.096094 -0.000000 +v 0.413603 0.079200 0.068750 +v 0.419000 0.079200 -0.000000 +v 0.396822 0.065006 0.065960 +v 0.402000 0.065006 -0.000000 +v 0.383003 0.053400 0.063663 +v 0.388000 0.053400 -0.000000 +v 0.373625 0.044269 0.062104 +v 0.378500 0.044269 -0.000000 +v 0.370170 0.037500 0.061530 +v 0.375000 0.037500 -0.000000 +v 0.471556 0.192919 0.158403 +v 0.462533 0.164100 0.155372 +v 0.449236 0.138431 0.150906 +v 0.433091 0.115800 0.145482 +v 0.415520 0.096094 0.139580 +v 0.397949 0.079200 0.133678 +v 0.381804 0.065006 0.128254 +v 0.368507 0.053400 0.123788 +v 0.359484 0.044269 0.120757 +v 0.356160 0.037500 0.119640 +v 0.441806 0.192919 0.229860 +v 0.433352 0.164100 0.225462 +v 0.420894 0.138431 0.218980 +v 0.405767 0.115800 0.211110 +v 0.389305 0.096094 0.202545 +v 0.372843 0.079200 0.193980 +v 0.357716 0.065006 0.186110 +v 0.345258 0.053400 0.179628 +v 0.336804 0.044269 0.175230 +v 0.333690 0.037500 0.173610 +v 0.401807 0.192919 0.294881 +v 0.394119 0.164100 0.289239 +v 0.382789 0.138431 0.280924 +v 0.369032 0.115800 0.270828 +v 0.354060 0.096094 0.259840 +v 0.339088 0.079200 0.248852 +v 0.325331 0.065006 0.238756 +v 0.314001 0.053400 0.230441 +v 0.306312 0.044269 0.224799 +v 0.303480 0.037500 0.222720 +v 0.352515 0.192919 0.352515 +v 0.345770 0.164100 0.345770 +v 0.335830 0.138431 0.335830 +v 0.323760 0.115800 0.323760 +v 0.310625 0.096094 0.310625 +v 0.297490 0.079200 0.297490 +v 0.285420 0.065006 0.285420 +v 0.275480 0.053400 0.275480 +v 0.268735 0.044269 0.268735 +v 0.266250 0.037500 0.266250 +v 0.294881 0.192919 0.401808 +v 0.289239 0.164100 0.394119 +v 0.280924 0.138431 0.382789 +v 0.270828 0.115800 0.369032 +v 0.259840 0.096094 0.354060 +v 0.248852 0.079200 0.339088 +v 0.238756 0.065006 0.325331 +v 0.230441 0.053400 0.314001 +v 0.224799 0.044269 0.306312 +v 0.222720 0.037500 0.303480 +v 0.229860 0.192919 0.441806 +v 0.225462 0.164100 0.433352 +v 0.218980 0.138431 0.420894 +v 0.211110 0.115800 0.405767 +v 0.202545 0.096094 0.389305 +v 0.193980 0.079200 0.372843 +v 0.186110 0.065006 0.357716 +v 0.179628 0.053400 0.345258 +v 0.175230 0.044269 0.336804 +v 0.173610 0.037500 0.333690 +v 0.158403 0.192919 0.471556 +v 0.155372 0.164100 0.462533 +v 0.150906 0.138431 0.449236 +v 0.145482 0.115800 0.433091 +v 0.139580 0.096094 0.415520 +v 0.133678 0.079200 0.397949 +v 0.128254 0.065006 0.381804 +v 0.123787 0.053400 0.368507 +v 0.120757 0.044269 0.359484 +v 0.119640 0.037500 0.356160 +v 0.081466 0.192919 0.490105 +v 0.079907 0.164100 0.480727 +v 0.077610 0.138431 0.466908 +v 0.074820 0.115800 0.450127 +v 0.071785 0.096094 0.431865 +v 0.068750 0.079200 0.413603 +v 0.065960 0.065006 0.396822 +v 0.063663 0.053400 0.383003 +v 0.062104 0.044269 0.373625 +v 0.061530 0.037500 0.370170 +v -0.000000 0.192919 0.496500 +v -0.000000 0.164100 0.487000 +v -0.000000 0.138431 0.473000 +v -0.000000 0.115800 0.456000 +v -0.000000 0.096094 0.437500 +v -0.000000 0.079200 0.419000 +v -0.000000 0.065006 0.402000 +v -0.000000 0.053400 0.388000 +v -0.000000 0.044269 0.378500 +v -0.000000 0.037500 0.375000 +v -0.081466 0.192919 0.490105 +v -0.079907 0.164100 0.480727 +v -0.077610 0.138431 0.466908 +v -0.074821 0.115800 0.450127 +v -0.071785 0.096094 0.431865 +v -0.068750 0.079200 0.413603 +v -0.065960 0.065006 0.396822 +v -0.063663 0.053400 0.383003 +v -0.062104 0.044269 0.373625 +v -0.061530 0.037500 0.370170 +v -0.158403 0.192919 0.471556 +v -0.155372 0.164100 0.462533 +v -0.150906 0.138431 0.449236 +v -0.145482 0.115800 0.433091 +v -0.139580 0.096094 0.415520 +v -0.133678 0.079200 0.397949 +v -0.128254 0.065006 0.381804 +v -0.123788 0.053400 0.368507 +v -0.120757 0.044269 0.359484 +v -0.119640 0.037500 0.356160 +v -0.229860 0.192919 0.441806 +v -0.225462 0.164100 0.433352 +v -0.218980 0.138431 0.420894 +v -0.211110 0.115800 0.405767 +v -0.202545 0.096094 0.389305 +v -0.193980 0.079200 0.372843 +v -0.186110 0.065006 0.357716 +v -0.179628 0.053400 0.345258 +v -0.175230 0.044269 0.336804 +v -0.173610 0.037500 0.333690 +v -0.294881 0.192919 0.401807 +v -0.289239 0.164100 0.394119 +v -0.280924 0.138431 0.382789 +v -0.270828 0.115800 0.369032 +v -0.259840 0.096094 0.354060 +v -0.248852 0.079200 0.339088 +v -0.238756 0.065006 0.325331 +v -0.230441 0.053400 0.314001 +v -0.224799 0.044269 0.306312 +v -0.222720 0.037500 0.303480 +v -0.352515 0.192919 0.352515 +v -0.345770 0.164100 0.345770 +v -0.335830 0.138431 0.335830 +v -0.323760 0.115800 0.323760 +v -0.310625 0.096094 0.310625 +v -0.297490 0.079200 0.297490 +v -0.285420 0.065006 0.285420 +v -0.275480 0.053400 0.275480 +v -0.268735 0.044269 0.268735 +v -0.266250 0.037500 0.266250 +v -0.401808 0.192919 0.294881 +v -0.394119 0.164100 0.289239 +v -0.382789 0.138431 0.280924 +v -0.369032 0.115800 0.270828 +v -0.354060 0.096094 0.259840 +v -0.339088 0.079200 0.248852 +v -0.325331 0.065006 0.238756 +v -0.314001 0.053400 0.230441 +v -0.306312 0.044269 0.224799 +v -0.303480 0.037500 0.222720 +v -0.441806 0.192919 0.229860 +v -0.433352 0.164100 0.225462 +v -0.420894 0.138431 0.218980 +v -0.405767 0.115800 0.211110 +v -0.389305 0.096094 0.202545 +v -0.372843 0.079200 0.193980 +v -0.357716 0.065006 0.186110 +v -0.345258 0.053400 0.179628 +v -0.336804 0.044269 0.175230 +v -0.333690 0.037500 0.173610 +v -0.471556 0.192919 0.158403 +v -0.462533 0.164100 0.155372 +v -0.449236 0.138431 0.150906 +v -0.433091 0.115800 0.145482 +v -0.415520 0.096094 0.139580 +v -0.397949 0.079200 0.133678 +v -0.381804 0.065006 0.128254 +v -0.368507 0.053400 0.123787 +v -0.359484 0.044269 0.120757 +v -0.356160 0.037500 0.119640 +v -0.490105 0.192919 0.081466 +v -0.480727 0.164100 0.079907 +v -0.466908 0.138431 0.077610 +v -0.450127 0.115800 0.074820 +v -0.431865 0.096094 0.071785 +v -0.413603 0.079200 0.068750 +v -0.396822 0.065006 0.065960 +v -0.383003 0.053400 0.063663 +v -0.373625 0.044269 0.062104 +v -0.370170 0.037500 0.061530 +v -0.496500 0.192919 -0.000000 +v -0.487000 0.164100 -0.000000 +v -0.473000 0.138431 -0.000000 +v -0.456000 0.115800 -0.000000 +v -0.437500 0.096094 -0.000000 +v -0.419000 0.079200 -0.000000 +v -0.402000 0.065006 -0.000000 +v -0.388000 0.053400 -0.000000 +v -0.378500 0.044269 -0.000000 +v -0.375000 0.037500 -0.000000 +v -0.490105 0.192919 -0.081466 +v -0.480727 0.164100 -0.079907 +v -0.466908 0.138431 -0.077610 +v -0.450127 0.115800 -0.074821 +v -0.431865 0.096094 -0.071785 +v -0.413603 0.079200 -0.068750 +v -0.396822 0.065006 -0.065960 +v -0.383003 0.053400 -0.063663 +v -0.373625 0.044269 -0.062104 +v -0.370170 0.037500 -0.061530 +v -0.471556 0.192919 -0.158403 +v -0.462533 0.164100 -0.155373 +v -0.449236 0.138431 -0.150906 +v -0.433091 0.115800 -0.145482 +v -0.415520 0.096094 -0.139580 +v -0.397949 0.079200 -0.133678 +v -0.381804 0.065006 -0.128254 +v -0.368507 0.053400 -0.123788 +v -0.359484 0.044269 -0.120757 +v -0.356160 0.037500 -0.119640 +v -0.441806 0.192919 -0.229860 +v -0.433352 0.164100 -0.225462 +v -0.420894 0.138431 -0.218980 +v -0.405767 0.115800 -0.211110 +v -0.389305 0.096094 -0.202545 +v -0.372843 0.079200 -0.193980 +v -0.357716 0.065006 -0.186110 +v -0.345258 0.053400 -0.179628 +v -0.336804 0.044269 -0.175230 +v -0.333690 0.037500 -0.173610 +v -0.401807 0.192919 -0.294881 +v -0.394119 0.164100 -0.289239 +v -0.382789 0.138431 -0.280924 +v -0.369032 0.115800 -0.270828 +v -0.354060 0.096094 -0.259840 +v -0.339088 0.079200 -0.248852 +v -0.325331 0.065006 -0.238756 +v -0.314001 0.053400 -0.230441 +v -0.306312 0.044269 -0.224799 +v -0.303480 0.037500 -0.222720 +v -0.352515 0.192919 -0.352515 +v -0.345770 0.164100 -0.345770 +v -0.335830 0.138431 -0.335830 +v -0.323760 0.115800 -0.323760 +v -0.310625 0.096094 -0.310625 +v -0.297490 0.079200 -0.297490 +v -0.285420 0.065006 -0.285420 +v -0.275480 0.053400 -0.275480 +v -0.268735 0.044269 -0.268735 +v -0.266250 0.037500 -0.266250 +v -0.294881 0.192919 -0.401808 +v -0.289239 0.164100 -0.394119 +v -0.280924 0.138431 -0.382789 +v -0.270828 0.115800 -0.369032 +v -0.259840 0.096094 -0.354060 +v -0.248852 0.079200 -0.339088 +v -0.238756 0.065006 -0.325331 +v -0.230441 0.053400 -0.314001 +v -0.224799 0.044269 -0.306312 +v -0.222720 0.037500 -0.303480 +v -0.229860 0.192919 -0.441806 +v -0.225462 0.164100 -0.433352 +v -0.218980 0.138431 -0.420894 +v -0.211110 0.115800 -0.405767 +v -0.202545 0.096094 -0.389305 +v -0.193980 0.079200 -0.372843 +v -0.186110 0.065006 -0.357716 +v -0.179628 0.053400 -0.345258 +v -0.175230 0.044269 -0.336804 +v -0.173610 0.037500 -0.333690 +v -0.158403 0.192919 -0.471556 +v -0.155372 0.164100 -0.462533 +v -0.150906 0.138431 -0.449236 +v -0.145482 0.115800 -0.433091 +v -0.139580 0.096094 -0.415520 +v -0.133678 0.079200 -0.397949 +v -0.128254 0.065006 -0.381804 +v -0.123787 0.053400 -0.368507 +v -0.120757 0.044269 -0.359484 +v -0.119640 0.037500 -0.356160 +v -0.081466 0.192919 -0.490105 +v -0.079907 0.164100 -0.480727 +v -0.077610 0.138431 -0.466908 +v -0.074820 0.115800 -0.450127 +v -0.071785 0.096094 -0.431865 +v -0.068750 0.079200 -0.413603 +v -0.065960 0.065006 -0.396822 +v -0.063663 0.053400 -0.383003 +v -0.062104 0.044269 -0.373625 +v -0.061530 0.037500 -0.370170 +v 0.000000 0.192919 -0.496500 +v 0.000000 0.164100 -0.487000 +v 0.000000 0.138431 -0.473000 +v 0.000000 0.115800 -0.456000 +v 0.000000 0.096094 -0.437500 +v 0.000000 0.079200 -0.419000 +v 0.000000 0.065006 -0.402000 +v 0.000000 0.053400 -0.388000 +v 0.000000 0.044269 -0.378500 +v 0.000000 0.037500 -0.375000 +v 0.081466 0.192919 -0.490105 +v 0.079907 0.164100 -0.480727 +v 0.077610 0.138431 -0.466908 +v 0.074821 0.115800 -0.450127 +v 0.071785 0.096094 -0.431865 +v 0.068750 0.079200 -0.413603 +v 0.065960 0.065006 -0.396822 +v 0.063663 0.053400 -0.383003 +v 0.062104 0.044269 -0.373625 +v 0.061530 0.037500 -0.370170 +v 0.158403 0.192919 -0.471556 +v 0.155372 0.164100 -0.462533 +v 0.150906 0.138431 -0.449236 +v 0.145482 0.115800 -0.433091 +v 0.139580 0.096094 -0.415520 +v 0.133678 0.079200 -0.397949 +v 0.128254 0.065006 -0.381804 +v 0.123788 0.053400 -0.368507 +v 0.120757 0.044269 -0.359484 +v 0.119640 0.037500 -0.356160 +v 0.229860 0.192919 -0.441806 +v 0.225462 0.164100 -0.433352 +v 0.218980 0.138431 -0.420894 +v 0.211110 0.115800 -0.405767 +v 0.202545 0.096094 -0.389305 +v 0.193980 0.079200 -0.372843 +v 0.186110 0.065006 -0.357716 +v 0.179628 0.053400 -0.345258 +v 0.175230 0.044269 -0.336804 +v 0.173610 0.037500 -0.333690 +v 0.294881 0.192919 -0.401807 +v 0.289239 0.164100 -0.394119 +v 0.280924 0.138431 -0.382789 +v 0.270828 0.115800 -0.369032 +v 0.259840 0.096094 -0.354060 +v 0.248852 0.079200 -0.339088 +v 0.238756 0.065006 -0.325331 +v 0.230441 0.053400 -0.314001 +v 0.224799 0.044269 -0.306312 +v 0.222720 0.037500 -0.303480 +v 0.352515 0.192919 -0.352515 +v 0.345770 0.164100 -0.345770 +v 0.335830 0.138431 -0.335830 +v 0.323760 0.115800 -0.323760 +v 0.310625 0.096094 -0.310625 +v 0.297490 0.079200 -0.297490 +v 0.285420 0.065006 -0.285420 +v 0.275480 0.053400 -0.275480 +v 0.268735 0.044269 -0.268735 +v 0.266250 0.037500 -0.266250 +v 0.401808 0.192919 -0.294881 +v 0.394119 0.164100 -0.289239 +v 0.382789 0.138431 -0.280924 +v 0.369032 0.115800 -0.270828 +v 0.354060 0.096094 -0.259840 +v 0.339088 0.079200 -0.248852 +v 0.325331 0.065006 -0.238756 +v 0.314001 0.053400 -0.230441 +v 0.306312 0.044269 -0.224799 +v 0.303480 0.037500 -0.222720 +v 0.441806 0.192919 -0.229860 +v 0.433352 0.164100 -0.225462 +v 0.420894 0.138431 -0.218980 +v 0.405767 0.115800 -0.211110 +v 0.389305 0.096094 -0.202545 +v 0.372843 0.079200 -0.193980 +v 0.357716 0.065006 -0.186110 +v 0.345258 0.053400 -0.179628 +v 0.336804 0.044269 -0.175230 +v 0.333690 0.037500 -0.173610 +v 0.471556 0.192919 -0.158403 +v 0.462533 0.164100 -0.155372 +v 0.449236 0.138431 -0.150906 +v 0.433091 0.115800 -0.145482 +v 0.415520 0.096094 -0.139580 +v 0.397949 0.079200 -0.133678 +v 0.381804 0.065006 -0.128254 +v 0.368507 0.053400 -0.123787 +v 0.359484 0.044269 -0.120757 +v 0.356160 0.037500 -0.119640 +v 0.490105 0.192919 -0.081466 +v 0.480727 0.164100 -0.079907 +v 0.466908 0.138431 -0.077610 +v 0.450127 0.115800 -0.074820 +v 0.431865 0.096094 -0.071785 +v 0.413603 0.079200 -0.068750 +v 0.396822 0.065006 -0.065960 +v 0.383003 0.053400 -0.063663 +v 0.373625 0.044269 -0.062104 +v 0.370170 0.037500 -0.061530 +v 0.369300 0.031894 0.061385 +v 0.374119 0.031894 -0.000000 +v 0.365432 0.026400 0.060742 +v 0.370200 0.026400 -0.000000 +v 0.356677 0.021131 0.059287 +v 0.361331 0.021131 -0.000000 +v 0.341149 0.016200 0.056706 +v 0.345600 0.016200 -0.000000 +v 0.316958 0.011719 0.052685 +v 0.321094 0.011719 -0.000000 +v 0.282218 0.007800 0.046911 +v 0.285900 0.007800 -0.000000 +v 0.235039 0.004556 0.039069 +v 0.238106 0.004556 -0.000000 +v 0.173536 0.002100 0.028845 +v 0.175800 0.002100 -0.000000 +v 0.095818 0.000544 0.015927 +v 0.097069 0.000544 -0.000000 +v 0.000000 -0.000000 0.000000 +v 0.355323 0.031894 0.119359 +v 0.351601 0.026400 0.118109 +v 0.343178 0.021131 0.115279 +v 0.328237 0.016200 0.110260 +v 0.304962 0.011719 0.102442 +v 0.271536 0.007800 0.091214 +v 0.226144 0.004556 0.075965 +v 0.166968 0.002100 0.056087 +v 0.092192 0.000544 0.030969 +v 0.332906 0.031894 0.173202 +v 0.329419 0.026400 0.171388 +v 0.321527 0.021131 0.167282 +v 0.307529 0.016200 0.159999 +v 0.285722 0.011719 0.148654 +v 0.254405 0.007800 0.132360 +v 0.211876 0.004556 0.110234 +v 0.156434 0.002100 0.081388 +v 0.086376 0.000544 0.044939 +v 0.302767 0.031894 0.222197 +v 0.299595 0.026400 0.219869 +v 0.292418 0.021131 0.214602 +v 0.279687 0.016200 0.205259 +v 0.259855 0.011719 0.190704 +v 0.231373 0.007800 0.169802 +v 0.192695 0.004556 0.141416 +v 0.142271 0.002100 0.104411 +v 0.078556 0.000544 0.057651 +v 0.265624 0.031894 0.265624 +v 0.262842 0.026400 0.262842 +v 0.256545 0.021131 0.256545 +v 0.245376 0.016200 0.245376 +v 0.227977 0.011719 0.227977 +v 0.202989 0.007800 0.202989 +v 0.169055 0.004556 0.169055 +v 0.124818 0.002100 0.124818 +v 0.068919 0.000544 0.068919 +v 0.222197 0.031894 0.302767 +v 0.219869 0.026400 0.299595 +v 0.214602 0.021131 0.292418 +v 0.205259 0.016200 0.279687 +v 0.190704 0.011719 0.259855 +v 0.169802 0.007800 0.231373 +v 0.141416 0.004556 0.192695 +v 0.104411 0.002100 0.142271 +v 0.057651 0.000544 0.078556 +v 0.173202 0.031894 0.332906 +v 0.171388 0.026400 0.329419 +v 0.167282 0.021131 0.321527 +v 0.159999 0.016200 0.307529 +v 0.148654 0.011719 0.285722 +v 0.132360 0.007800 0.254405 +v 0.110234 0.004556 0.211876 +v 0.081388 0.002100 0.156434 +v 0.044939 0.000544 0.086376 +v 0.119359 0.031894 0.355323 +v 0.118109 0.026400 0.351601 +v 0.115279 0.021131 0.343178 +v 0.110260 0.016200 0.328237 +v 0.102442 0.011719 0.304962 +v 0.091214 0.007800 0.271536 +v 0.075965 0.004556 0.226144 +v 0.056087 0.002100 0.166968 +v 0.030969 0.000544 0.092192 +v 0.061385 0.031894 0.369300 +v 0.060742 0.026400 0.365432 +v 0.059287 0.021131 0.356677 +v 0.056706 0.016200 0.341149 +v 0.052685 0.011719 0.316958 +v 0.046910 0.007800 0.282218 +v 0.039068 0.004556 0.235039 +v 0.028845 0.002100 0.173536 +v 0.015927 0.000544 0.095818 +v -0.000000 0.031894 0.374119 +v -0.000000 0.026400 0.370200 +v -0.000000 0.021131 0.361331 +v -0.000000 0.016200 0.345600 +v -0.000000 0.011719 0.321094 +v -0.000000 0.007800 0.285900 +v -0.000000 0.004556 0.238106 +v -0.000000 0.002100 0.175800 +v -0.000000 0.000544 0.097069 +v -0.061385 0.031894 0.369300 +v -0.060742 0.026400 0.365432 +v -0.059287 0.021131 0.356677 +v -0.056706 0.016200 0.341149 +v -0.052685 0.011719 0.316958 +v -0.046911 0.007800 0.282218 +v -0.039069 0.004556 0.235039 +v -0.028845 0.002100 0.173536 +v -0.015927 0.000544 0.095818 +v -0.119359 0.031894 0.355323 +v -0.118109 0.026400 0.351601 +v -0.115279 0.021131 0.343178 +v -0.110260 0.016200 0.328237 +v -0.102442 0.011719 0.304962 +v -0.091214 0.007800 0.271536 +v -0.075965 0.004556 0.226144 +v -0.056087 0.002100 0.166968 +v -0.030969 0.000544 0.092192 +v -0.173202 0.031894 0.332906 +v -0.171388 0.026400 0.329419 +v -0.167282 0.021131 0.321527 +v -0.159999 0.016200 0.307529 +v -0.148654 0.011719 0.285722 +v -0.132360 0.007800 0.254405 +v -0.110234 0.004556 0.211876 +v -0.081388 0.002100 0.156434 +v -0.044939 0.000544 0.086376 +v -0.222197 0.031894 0.302767 +v -0.219869 0.026400 0.299595 +v -0.214602 0.021131 0.292418 +v -0.205259 0.016200 0.279687 +v -0.190704 0.011719 0.259855 +v -0.169802 0.007800 0.231373 +v -0.141416 0.004556 0.192695 +v -0.104411 0.002100 0.142271 +v -0.057651 0.000544 0.078556 +v -0.265624 0.031894 0.265624 +v -0.262842 0.026400 0.262842 +v -0.256545 0.021131 0.256545 +v -0.245376 0.016200 0.245376 +v -0.227977 0.011719 0.227977 +v -0.202989 0.007800 0.202989 +v -0.169055 0.004556 0.169055 +v -0.124818 0.002100 0.124818 +v -0.068919 0.000544 0.068919 +v -0.302767 0.031894 0.222197 +v -0.299595 0.026400 0.219869 +v -0.292418 0.021131 0.214602 +v -0.279687 0.016200 0.205259 +v -0.259855 0.011719 0.190704 +v -0.231373 0.007800 0.169802 +v -0.192695 0.004556 0.141416 +v -0.142271 0.002100 0.104411 +v -0.078556 0.000544 0.057651 +v -0.332906 0.031894 0.173202 +v -0.329419 0.026400 0.171388 +v -0.321527 0.021131 0.167282 +v -0.307529 0.016200 0.159999 +v -0.285722 0.011719 0.148654 +v -0.254405 0.007800 0.132360 +v -0.211876 0.004556 0.110234 +v -0.156434 0.002100 0.081388 +v -0.086376 0.000544 0.044939 +v -0.355323 0.031894 0.119359 +v -0.351601 0.026400 0.118109 +v -0.343178 0.021131 0.115279 +v -0.328237 0.016200 0.110260 +v -0.304962 0.011719 0.102442 +v -0.271536 0.007800 0.091214 +v -0.226144 0.004556 0.075965 +v -0.166968 0.002100 0.056087 +v -0.092192 0.000544 0.030969 +v -0.369300 0.031894 0.061385 +v -0.365432 0.026400 0.060742 +v -0.356677 0.021131 0.059287 +v -0.341149 0.016200 0.056706 +v -0.316958 0.011719 0.052685 +v -0.282218 0.007800 0.046910 +v -0.235039 0.004556 0.039068 +v -0.173536 0.002100 0.028845 +v -0.095818 0.000544 0.015927 +v -0.374119 0.031894 -0.000000 +v -0.370200 0.026400 -0.000000 +v -0.361331 0.021131 -0.000000 +v -0.345600 0.016200 -0.000000 +v -0.321094 0.011719 -0.000000 +v -0.285900 0.007800 -0.000000 +v -0.238106 0.004556 -0.000000 +v -0.175800 0.002100 -0.000000 +v -0.097069 0.000544 -0.000000 +v -0.369300 0.031894 -0.061385 +v -0.365432 0.026400 -0.060742 +v -0.356677 0.021131 -0.059287 +v -0.341149 0.016200 -0.056706 +v -0.316958 0.011719 -0.052685 +v -0.282218 0.007800 -0.046911 +v -0.235039 0.004556 -0.039069 +v -0.173536 0.002100 -0.028845 +v -0.095818 0.000544 -0.015927 +v -0.355323 0.031894 -0.119359 +v -0.351601 0.026400 -0.118109 +v -0.343178 0.021131 -0.115279 +v -0.328237 0.016200 -0.110260 +v -0.304962 0.011719 -0.102442 +v -0.271536 0.007800 -0.091214 +v -0.226144 0.004556 -0.075965 +v -0.166968 0.002100 -0.056087 +v -0.092192 0.000544 -0.030969 +v -0.332906 0.031894 -0.173202 +v -0.329419 0.026400 -0.171388 +v -0.321527 0.021131 -0.167282 +v -0.307529 0.016200 -0.159999 +v -0.285722 0.011719 -0.148654 +v -0.254405 0.007800 -0.132360 +v -0.211876 0.004556 -0.110234 +v -0.156434 0.002100 -0.081388 +v -0.086376 0.000544 -0.044939 +v -0.302767 0.031894 -0.222197 +v -0.299595 0.026400 -0.219869 +v -0.292418 0.021131 -0.214602 +v -0.279687 0.016200 -0.205259 +v -0.259855 0.011719 -0.190704 +v -0.231373 0.007800 -0.169802 +v -0.192695 0.004556 -0.141416 +v -0.142271 0.002100 -0.104411 +v -0.078556 0.000544 -0.057651 +v -0.265624 0.031894 -0.265624 +v -0.262842 0.026400 -0.262842 +v -0.256545 0.021131 -0.256545 +v -0.245376 0.016200 -0.245376 +v -0.227977 0.011719 -0.227977 +v -0.202989 0.007800 -0.202989 +v -0.169055 0.004556 -0.169055 +v -0.124818 0.002100 -0.124818 +v -0.068919 0.000544 -0.068919 +v -0.222197 0.031894 -0.302767 +v -0.219869 0.026400 -0.299595 +v -0.214602 0.021131 -0.292418 +v -0.205259 0.016200 -0.279687 +v -0.190704 0.011719 -0.259855 +v -0.169802 0.007800 -0.231373 +v -0.141416 0.004556 -0.192695 +v -0.104411 0.002100 -0.142271 +v -0.057651 0.000544 -0.078556 +v -0.173202 0.031894 -0.332906 +v -0.171388 0.026400 -0.329419 +v -0.167282 0.021131 -0.321527 +v -0.159999 0.016200 -0.307529 +v -0.148654 0.011719 -0.285722 +v -0.132360 0.007800 -0.254405 +v -0.110234 0.004556 -0.211876 +v -0.081388 0.002100 -0.156434 +v -0.044939 0.000544 -0.086376 +v -0.119359 0.031894 -0.355323 +v -0.118109 0.026400 -0.351601 +v -0.115279 0.021131 -0.343178 +v -0.110260 0.016200 -0.328237 +v -0.102442 0.011719 -0.304962 +v -0.091214 0.007800 -0.271536 +v -0.075965 0.004556 -0.226144 +v -0.056087 0.002100 -0.166968 +v -0.030969 0.000544 -0.092192 +v -0.061385 0.031894 -0.369300 +v -0.060742 0.026400 -0.365432 +v -0.059287 0.021131 -0.356677 +v -0.056706 0.016200 -0.341149 +v -0.052685 0.011719 -0.316958 +v -0.046910 0.007800 -0.282218 +v -0.039068 0.004556 -0.235039 +v -0.028845 0.002100 -0.173536 +v -0.015927 0.000544 -0.095818 +v 0.000000 0.031894 -0.374119 +v 0.000000 0.026400 -0.370200 +v 0.000000 0.021131 -0.361331 +v 0.000000 0.016200 -0.345600 +v 0.000000 0.011719 -0.321094 +v 0.000000 0.007800 -0.285900 +v 0.000000 0.004556 -0.238106 +v 0.000000 0.002100 -0.175800 +v 0.000000 0.000544 -0.097069 +v 0.061385 0.031894 -0.369300 +v 0.060742 0.026400 -0.365432 +v 0.059287 0.021131 -0.356677 +v 0.056706 0.016200 -0.341149 +v 0.052685 0.011719 -0.316958 +v 0.046911 0.007800 -0.282218 +v 0.039069 0.004556 -0.235039 +v 0.028845 0.002100 -0.173536 +v 0.015927 0.000544 -0.095818 +v 0.119359 0.031894 -0.355323 +v 0.118109 0.026400 -0.351601 +v 0.115279 0.021131 -0.343178 +v 0.110260 0.016200 -0.328237 +v 0.102442 0.011719 -0.304962 +v 0.091214 0.007800 -0.271536 +v 0.075965 0.004556 -0.226144 +v 0.056087 0.002100 -0.166968 +v 0.030969 0.000544 -0.092192 +v 0.173202 0.031894 -0.332906 +v 0.171388 0.026400 -0.329419 +v 0.167282 0.021131 -0.321527 +v 0.159999 0.016200 -0.307529 +v 0.148654 0.011719 -0.285722 +v 0.132360 0.007800 -0.254405 +v 0.110234 0.004556 -0.211876 +v 0.081388 0.002100 -0.156434 +v 0.044939 0.000544 -0.086376 +v 0.222197 0.031894 -0.302767 +v 0.219869 0.026400 -0.299595 +v 0.214602 0.021131 -0.292418 +v 0.205259 0.016200 -0.279687 +v 0.190704 0.011719 -0.259855 +v 0.169802 0.007800 -0.231373 +v 0.141416 0.004556 -0.192695 +v 0.104411 0.002100 -0.142271 +v 0.057651 0.000544 -0.078556 +v 0.265624 0.031894 -0.265624 +v 0.262842 0.026400 -0.262842 +v 0.256545 0.021131 -0.256545 +v 0.245376 0.016200 -0.245376 +v 0.227977 0.011719 -0.227977 +v 0.202989 0.007800 -0.202989 +v 0.169055 0.004556 -0.169055 +v 0.124818 0.002100 -0.124818 +v 0.068919 0.000544 -0.068919 +v 0.302767 0.031894 -0.222197 +v 0.299595 0.026400 -0.219869 +v 0.292418 0.021131 -0.214602 +v 0.279687 0.016200 -0.205259 +v 0.259855 0.011719 -0.190704 +v 0.231373 0.007800 -0.169802 +v 0.192695 0.004556 -0.141416 +v 0.142271 0.002100 -0.104411 +v 0.078556 0.000544 -0.057651 +v 0.332906 0.031894 -0.173202 +v 0.329419 0.026400 -0.171388 +v 0.321527 0.021131 -0.167282 +v 0.307529 0.016200 -0.159999 +v 0.285722 0.011719 -0.148654 +v 0.254405 0.007800 -0.132360 +v 0.211876 0.004556 -0.110234 +v 0.156434 0.002100 -0.081388 +v 0.086376 0.000544 -0.044939 +v 0.355323 0.031894 -0.119359 +v 0.351601 0.026400 -0.118109 +v 0.343178 0.021131 -0.115279 +v 0.328237 0.016200 -0.110260 +v 0.304962 0.011719 -0.102442 +v 0.271536 0.007800 -0.091214 +v 0.226144 0.004556 -0.075965 +v 0.166968 0.002100 -0.056087 +v 0.092192 0.000544 -0.030969 +v 0.369300 0.031894 -0.061385 +v 0.365432 0.026400 -0.060742 +v 0.356677 0.021131 -0.059287 +v 0.341149 0.016200 -0.056706 +v 0.316958 0.011719 -0.052685 +v 0.282218 0.007800 -0.046910 +v 0.235039 0.004556 -0.039068 +v 0.173536 0.002100 -0.028845 +v 0.095818 0.000544 -0.015927 +v -0.400000 0.506250 -0.000000 +v -0.399300 0.507825 0.020250 +v -0.450114 0.507767 0.020250 +v -0.450225 0.506194 -0.000000 +v -0.496198 0.507362 0.020250 +v -0.495800 0.505800 -0.000000 +v -0.537406 0.506264 0.020250 +v -0.536575 0.504731 -0.000000 +v -0.573593 0.504124 0.020250 +v -0.572400 0.502650 -0.000000 +v -0.604612 0.500597 0.020250 +v -0.603125 0.499219 -0.000000 +v -0.630319 0.495335 0.020250 +v -0.628600 0.494100 -0.000000 +v -0.650567 0.487991 0.020250 +v -0.648675 0.486956 -0.000000 +v -0.665210 0.478219 0.020250 +v -0.663200 0.477450 -0.000000 +v -0.674103 0.465671 0.020250 +v -0.672025 0.465244 -0.000000 +v -0.677100 0.450000 0.020250 +v -0.675000 0.450000 -0.000000 +v -0.397400 0.512100 0.036000 +v -0.449812 0.512038 0.036000 +v -0.497277 0.511603 0.036000 +v -0.539661 0.510423 0.036000 +v -0.576830 0.508126 0.036000 +v -0.608650 0.504338 0.036000 +v -0.634986 0.498686 0.036000 +v -0.655703 0.490800 0.036000 +v -0.670667 0.480305 0.036000 +v -0.679744 0.466829 0.036000 +v -0.682800 0.450000 0.036000 +v -0.394600 0.518400 0.047250 +v -0.449366 0.518332 0.047250 +v -0.498867 0.517853 0.047250 +v -0.542985 0.516553 0.047250 +v -0.581602 0.514022 0.047250 +v -0.614600 0.509850 0.047250 +v -0.641862 0.503626 0.047250 +v -0.663271 0.494939 0.047250 +v -0.678709 0.483379 0.047250 +v -0.688058 0.468536 0.047250 +v -0.691200 0.450000 0.047250 +v -0.391200 0.526050 0.054000 +v -0.448826 0.525974 0.054000 +v -0.500798 0.525442 0.054000 +v -0.547021 0.523997 0.054000 +v -0.587395 0.521183 0.054000 +v -0.621825 0.516544 0.054000 +v -0.650213 0.509623 0.054000 +v -0.672461 0.499965 0.054000 +v -0.688474 0.487112 0.054000 +v -0.698152 0.470610 0.054000 +v -0.701400 0.450000 0.054000 +v -0.387500 0.534375 0.056250 +v -0.448238 0.534291 0.056250 +v -0.502900 0.533700 0.056250 +v -0.551413 0.532097 0.056250 +v -0.593700 0.528975 0.056250 +v -0.629688 0.523828 0.056250 +v -0.659300 0.516150 0.056250 +v -0.682463 0.505434 0.056250 +v -0.699100 0.491175 0.056250 +v -0.709137 0.472866 0.056250 +v -0.712500 0.450000 0.056250 +v -0.383800 0.542700 0.054000 +v -0.447649 0.542607 0.054000 +v -0.505002 0.541958 0.054000 +v -0.555804 0.540197 0.054000 +v -0.600005 0.536767 0.054000 +v -0.637550 0.531112 0.054000 +v -0.668387 0.522677 0.054000 +v -0.692464 0.510904 0.054000 +v -0.709726 0.495238 0.054000 +v -0.720123 0.475122 0.054000 +v -0.723600 0.450000 0.054000 +v -0.380400 0.550350 0.047250 +v -0.447109 0.550250 0.047250 +v -0.506933 0.549547 0.047250 +v -0.559840 0.547641 0.047250 +v -0.605798 0.543928 0.047250 +v -0.644775 0.537806 0.047250 +v -0.676738 0.528674 0.047250 +v -0.701654 0.515930 0.047250 +v -0.719491 0.498971 0.047250 +v -0.730217 0.477195 0.047250 +v -0.733800 0.450000 0.047250 +v -0.377600 0.556650 0.036000 +v -0.446663 0.556543 0.036000 +v -0.508523 0.555797 0.036000 +v -0.563164 0.553770 0.036000 +v -0.610570 0.549824 0.036000 +v -0.650725 0.543319 0.036000 +v -0.683614 0.533614 0.036000 +v -0.709222 0.520069 0.036000 +v -0.727533 0.502045 0.036000 +v -0.738531 0.478902 0.036000 +v -0.742200 0.450000 0.036000 +v -0.375700 0.560925 0.020250 +v -0.446361 0.560814 0.020250 +v -0.509602 0.560038 0.020250 +v -0.565419 0.557930 0.020250 +v -0.613807 0.553826 0.020250 +v -0.654763 0.547059 0.020250 +v -0.688281 0.536965 0.020250 +v -0.714358 0.522878 0.020250 +v -0.732990 0.504131 0.020250 +v -0.744172 0.480061 0.020250 +v -0.747900 0.450000 0.020250 +v -0.375000 0.562500 -0.000000 +v -0.446250 0.562388 -0.000000 +v -0.510000 0.561600 -0.000000 +v -0.566250 0.559462 -0.000000 +v -0.615000 0.555300 -0.000000 +v -0.656250 0.548438 -0.000000 +v -0.690000 0.538200 -0.000000 +v -0.716250 0.523912 -0.000000 +v -0.735000 0.504900 -0.000000 +v -0.746250 0.480487 -0.000000 +v -0.750000 0.450000 -0.000000 +v -0.375700 0.560925 -0.020250 +v -0.446361 0.560814 -0.020250 +v -0.509602 0.560038 -0.020250 +v -0.565419 0.557930 -0.020250 +v -0.613807 0.553826 -0.020250 +v -0.654763 0.547059 -0.020250 +v -0.688281 0.536965 -0.020250 +v -0.714358 0.522878 -0.020250 +v -0.732990 0.504131 -0.020250 +v -0.744172 0.480061 -0.020250 +v -0.747900 0.450000 -0.020250 +v -0.377600 0.556650 -0.036000 +v -0.446663 0.556543 -0.036000 +v -0.508523 0.555797 -0.036000 +v -0.563164 0.553770 -0.036000 +v -0.610570 0.549824 -0.036000 +v -0.650725 0.543319 -0.036000 +v -0.683614 0.533614 -0.036000 +v -0.709222 0.520069 -0.036000 +v -0.727533 0.502045 -0.036000 +v -0.738531 0.478902 -0.036000 +v -0.742200 0.450000 -0.036000 +v -0.380400 0.550350 -0.047250 +v -0.447109 0.550250 -0.047250 +v -0.506933 0.549547 -0.047250 +v -0.559840 0.547641 -0.047250 +v -0.605798 0.543928 -0.047250 +v -0.644775 0.537806 -0.047250 +v -0.676738 0.528674 -0.047250 +v -0.701654 0.515930 -0.047250 +v -0.719491 0.498971 -0.047250 +v -0.730217 0.477195 -0.047250 +v -0.733800 0.450000 -0.047250 +v -0.383800 0.542700 -0.054000 +v -0.447649 0.542607 -0.054000 +v -0.505002 0.541958 -0.054000 +v -0.555804 0.540197 -0.054000 +v -0.600005 0.536767 -0.054000 +v -0.637550 0.531112 -0.054000 +v -0.668387 0.522677 -0.054000 +v -0.692464 0.510904 -0.054000 +v -0.709726 0.495238 -0.054000 +v -0.720123 0.475122 -0.054000 +v -0.723600 0.450000 -0.054000 +v -0.387500 0.534375 -0.056250 +v -0.448238 0.534291 -0.056250 +v -0.502900 0.533700 -0.056250 +v -0.551413 0.532097 -0.056250 +v -0.593700 0.528975 -0.056250 +v -0.629688 0.523828 -0.056250 +v -0.659300 0.516150 -0.056250 +v -0.682463 0.505434 -0.056250 +v -0.699100 0.491175 -0.056250 +v -0.709137 0.472866 -0.056250 +v -0.712500 0.450000 -0.056250 +v -0.391200 0.526050 -0.054000 +v -0.448826 0.525974 -0.054000 +v -0.500798 0.525442 -0.054000 +v -0.547021 0.523997 -0.054000 +v -0.587395 0.521183 -0.054000 +v -0.621825 0.516544 -0.054000 +v -0.650213 0.509623 -0.054000 +v -0.672461 0.499965 -0.054000 +v -0.688474 0.487112 -0.054000 +v -0.698152 0.470610 -0.054000 +v -0.701400 0.450000 -0.054000 +v -0.394600 0.518400 -0.047250 +v -0.449366 0.518332 -0.047250 +v -0.498867 0.517853 -0.047250 +v -0.542985 0.516553 -0.047250 +v -0.581602 0.514022 -0.047250 +v -0.614600 0.509850 -0.047250 +v -0.641862 0.503626 -0.047250 +v -0.663271 0.494939 -0.047250 +v -0.678709 0.483379 -0.047250 +v -0.688058 0.468536 -0.047250 +v -0.691200 0.450000 -0.047250 +v -0.397400 0.512100 -0.036000 +v -0.449812 0.512038 -0.036000 +v -0.497277 0.511603 -0.036000 +v -0.539661 0.510423 -0.036000 +v -0.576830 0.508126 -0.036000 +v -0.608650 0.504338 -0.036000 +v -0.634986 0.498686 -0.036000 +v -0.655703 0.490800 -0.036000 +v -0.670667 0.480305 -0.036000 +v -0.679744 0.466829 -0.036000 +v -0.682800 0.450000 -0.036000 +v -0.399300 0.507825 -0.020250 +v -0.450114 0.507767 -0.020250 +v -0.496198 0.507362 -0.020250 +v -0.537406 0.506264 -0.020250 +v -0.573593 0.504124 -0.020250 +v -0.604612 0.500597 -0.020250 +v -0.630319 0.495335 -0.020250 +v -0.650567 0.487991 -0.020250 +v -0.665210 0.478219 -0.020250 +v -0.674103 0.465671 -0.020250 +v -0.677100 0.450000 -0.020250 +v -0.675544 0.431130 0.020250 +v -0.673475 0.431550 -0.000000 +v -0.670777 0.409652 0.020250 +v -0.668800 0.410400 -0.000000 +v -0.662651 0.386226 0.020250 +v -0.660825 0.387225 -0.000000 +v -0.651018 0.361507 0.020250 +v -0.649400 0.362700 -0.000000 +v -0.635731 0.336155 0.020250 +v -0.634375 0.337500 -0.000000 +v -0.616642 0.310826 0.020250 +v -0.615600 0.312300 -0.000000 +v -0.593602 0.286178 0.020250 +v -0.592925 0.287775 -0.000000 +v -0.566463 0.262870 0.020250 +v -0.566200 0.264600 -0.000000 +v -0.535079 0.241558 0.020250 +v -0.535275 0.243450 -0.000000 +v -0.499300 0.222900 0.020250 +v -0.500000 0.225000 -0.000000 +v -0.681159 0.429989 0.036000 +v -0.676142 0.407623 0.036000 +v -0.667607 0.383513 0.036000 +v -0.655411 0.358270 0.036000 +v -0.639412 0.332503 0.036000 +v -0.619469 0.306824 0.036000 +v -0.595438 0.281844 0.036000 +v -0.567178 0.258173 0.036000 +v -0.534546 0.236421 0.036000 +v -0.497400 0.217200 0.036000 +v -0.689435 0.428308 0.047250 +v -0.684050 0.404633 0.047250 +v -0.674911 0.379516 0.047250 +v -0.661885 0.353498 0.047250 +v -0.644837 0.327122 0.047250 +v -0.623635 0.300928 0.047250 +v -0.598144 0.275457 0.047250 +v -0.568230 0.251251 0.047250 +v -0.533760 0.228852 0.047250 +v -0.494600 0.208800 0.047250 +v -0.699483 0.426267 0.054000 +v -0.693651 0.401002 0.054000 +v -0.683780 0.374662 0.054000 +v -0.669746 0.347705 0.054000 +v -0.651425 0.320588 0.054000 +v -0.628694 0.293767 0.054000 +v -0.601430 0.267701 0.054000 +v -0.569509 0.242846 0.054000 +v -0.532807 0.219660 0.054000 +v -0.491200 0.198600 0.054000 +v -0.710419 0.424045 0.056250 +v -0.704100 0.397050 0.056250 +v -0.693431 0.369380 0.056250 +v -0.678300 0.341400 0.056250 +v -0.658594 0.313477 0.056250 +v -0.634200 0.285975 0.056250 +v -0.605006 0.259261 0.056250 +v -0.570900 0.233700 0.056250 +v -0.531769 0.209658 0.056250 +v -0.487500 0.187500 0.056250 +v -0.721354 0.421824 0.054000 +v -0.714549 0.393098 0.054000 +v -0.703083 0.364097 0.054000 +v -0.686854 0.335095 0.054000 +v -0.665763 0.306366 0.054000 +v -0.639706 0.278183 0.054000 +v -0.608582 0.250821 0.054000 +v -0.572291 0.224554 0.054000 +v -0.530731 0.199655 0.054000 +v -0.483800 0.176400 0.054000 +v -0.731403 0.419783 0.047250 +v -0.724150 0.389467 0.047250 +v -0.711952 0.359244 0.047250 +v -0.694715 0.329302 0.047250 +v -0.672350 0.299831 0.047250 +v -0.644765 0.271022 0.047250 +v -0.611868 0.243065 0.047250 +v -0.573570 0.216149 0.047250 +v -0.529777 0.190464 0.047250 +v -0.480400 0.166200 0.047250 +v -0.739678 0.418102 0.036000 +v -0.732058 0.386477 0.036000 +v -0.719255 0.355246 0.036000 +v -0.701189 0.324530 0.036000 +v -0.677775 0.294450 0.036000 +v -0.648931 0.265126 0.036000 +v -0.614575 0.236678 0.036000 +v -0.574622 0.209227 0.036000 +v -0.528992 0.182894 0.036000 +v -0.477600 0.157800 0.036000 +v -0.745294 0.416961 0.020250 +v -0.737423 0.384448 0.020250 +v -0.724212 0.352534 0.020250 +v -0.705582 0.321293 0.020250 +v -0.681456 0.290798 0.020250 +v -0.651758 0.261124 0.020250 +v -0.616411 0.232344 0.020250 +v -0.575337 0.204530 0.020250 +v -0.528459 0.177758 0.020250 +v -0.475700 0.152100 0.020250 +v -0.747363 0.416541 -0.000000 +v -0.739400 0.383700 -0.000000 +v -0.726038 0.351534 -0.000000 +v -0.707200 0.320100 -0.000000 +v -0.682812 0.289453 -0.000000 +v -0.652800 0.259650 -0.000000 +v -0.617087 0.230747 -0.000000 +v -0.575600 0.202800 -0.000000 +v -0.528262 0.175866 -0.000000 +v -0.475000 0.150000 -0.000000 +v -0.745294 0.416961 -0.020250 +v -0.737423 0.384448 -0.020250 +v -0.724212 0.352534 -0.020250 +v -0.705582 0.321293 -0.020250 +v -0.681456 0.290798 -0.020250 +v -0.651758 0.261124 -0.020250 +v -0.616411 0.232344 -0.020250 +v -0.575337 0.204530 -0.020250 +v -0.528459 0.177758 -0.020250 +v -0.475700 0.152100 -0.020250 +v -0.739678 0.418102 -0.036000 +v -0.732058 0.386477 -0.036000 +v -0.719255 0.355246 -0.036000 +v -0.701189 0.324530 -0.036000 +v -0.677775 0.294450 -0.036000 +v -0.648931 0.265126 -0.036000 +v -0.614575 0.236678 -0.036000 +v -0.574622 0.209227 -0.036000 +v -0.528992 0.182894 -0.036000 +v -0.477600 0.157800 -0.036000 +v -0.731403 0.419783 -0.047250 +v -0.724150 0.389467 -0.047250 +v -0.711952 0.359244 -0.047250 +v -0.694715 0.329302 -0.047250 +v -0.672350 0.299831 -0.047250 +v -0.644765 0.271022 -0.047250 +v -0.611868 0.243065 -0.047250 +v -0.573570 0.216149 -0.047250 +v -0.529777 0.190464 -0.047250 +v -0.480400 0.166200 -0.047250 +v -0.721354 0.421824 -0.054000 +v -0.714549 0.393098 -0.054000 +v -0.703083 0.364097 -0.054000 +v -0.686854 0.335095 -0.054000 +v -0.665763 0.306366 -0.054000 +v -0.639706 0.278183 -0.054000 +v -0.608582 0.250821 -0.054000 +v -0.572291 0.224554 -0.054000 +v -0.530731 0.199655 -0.054000 +v -0.483800 0.176400 -0.054000 +v -0.710419 0.424045 -0.056250 +v -0.704100 0.397050 -0.056250 +v -0.693431 0.369380 -0.056250 +v -0.678300 0.341400 -0.056250 +v -0.658594 0.313477 -0.056250 +v -0.634200 0.285975 -0.056250 +v -0.605006 0.259261 -0.056250 +v -0.570900 0.233700 -0.056250 +v -0.531769 0.209658 -0.056250 +v -0.487500 0.187500 -0.056250 +v -0.699483 0.426267 -0.054000 +v -0.693651 0.401002 -0.054000 +v -0.683780 0.374662 -0.054000 +v -0.669746 0.347705 -0.054000 +v -0.651425 0.320588 -0.054000 +v -0.628694 0.293767 -0.054000 +v -0.601430 0.267701 -0.054000 +v -0.569509 0.242846 -0.054000 +v -0.532807 0.219660 -0.054000 +v -0.491200 0.198600 -0.054000 +v -0.689435 0.428308 -0.047250 +v -0.684050 0.404633 -0.047250 +v -0.674911 0.379516 -0.047250 +v -0.661885 0.353498 -0.047250 +v -0.644837 0.327122 -0.047250 +v -0.623635 0.300928 -0.047250 +v -0.598144 0.275457 -0.047250 +v -0.568230 0.251251 -0.047250 +v -0.533760 0.228852 -0.047250 +v -0.494600 0.208800 -0.047250 +v -0.681159 0.429989 -0.036000 +v -0.676142 0.407623 -0.036000 +v -0.667607 0.383513 -0.036000 +v -0.655411 0.358270 -0.036000 +v -0.639412 0.332503 -0.036000 +v -0.619469 0.306824 -0.036000 +v -0.595438 0.281844 -0.036000 +v -0.567178 0.258173 -0.036000 +v -0.534546 0.236421 -0.036000 +v -0.497400 0.217200 -0.036000 +v -0.675544 0.431130 -0.020250 +v -0.670777 0.409652 -0.020250 +v -0.662651 0.386226 -0.020250 +v -0.651018 0.361507 -0.020250 +v -0.635731 0.336155 -0.020250 +v -0.616642 0.310826 -0.020250 +v -0.593602 0.286178 -0.020250 +v -0.566463 0.262870 -0.020250 +v -0.535079 0.241558 -0.020250 +v -0.499300 0.222900 -0.020250 +v 0.425000 0.356250 -0.000000 +v 0.425000 0.350475 0.044550 +v 0.484849 0.355805 0.043775 +v 0.483975 0.361050 -0.000000 +v 0.529245 0.369780 0.041672 +v 0.527800 0.374400 -0.000000 +v 0.561114 0.390793 0.038572 +v 0.559325 0.394725 -0.000000 +v 0.583382 0.417237 0.034808 +v 0.581400 0.420450 -0.000000 +v 0.598975 0.447506 0.030712 +v 0.596875 0.450000 -0.000000 +v 0.610818 0.479994 0.026617 +v 0.608600 0.481800 -0.000000 +v 0.621836 0.513094 0.022853 +v 0.619425 0.514275 -0.000000 +v 0.634955 0.545199 0.019753 +v 0.632200 0.545850 -0.000000 +v 0.653101 0.574703 0.017650 +v 0.649775 0.574950 -0.000000 +v 0.679200 0.600000 0.016875 +v 0.675000 0.600000 -0.000000 +v 0.425000 0.334800 0.079200 +v 0.487220 0.341569 0.077822 +v 0.533166 0.357240 0.074083 +v 0.565971 0.380120 0.068573 +v 0.588763 0.408516 0.061882 +v 0.604675 0.440737 0.054600 +v 0.616837 0.475092 0.047318 +v 0.628379 0.509888 0.040627 +v 0.642434 0.543432 0.035117 +v 0.662130 0.574034 0.031378 +v 0.690600 0.600000 0.030000 +v 0.425000 0.311700 0.103950 +v 0.490714 0.320590 0.102142 +v 0.538946 0.338760 0.097234 +v 0.573127 0.364390 0.090002 +v 0.596693 0.395664 0.081220 +v 0.613075 0.430762 0.071662 +v 0.625707 0.467868 0.062105 +v 0.638023 0.505162 0.053323 +v 0.653454 0.540828 0.046091 +v 0.675436 0.573047 0.041183 +v 0.707400 0.600000 0.039375 +v 0.425000 0.283650 0.118800 +v 0.494957 0.295116 0.116734 +v 0.545963 0.316320 0.111125 +v 0.581818 0.345291 0.102859 +v 0.606322 0.380058 0.092822 +v 0.623275 0.418650 0.081900 +v 0.636478 0.459096 0.070978 +v 0.649732 0.499425 0.060941 +v 0.666837 0.537666 0.052675 +v 0.691593 0.571848 0.047066 +v 0.727800 0.600000 0.045000 +v 0.425000 0.253125 0.123750 +v 0.499575 0.267394 0.121597 +v 0.553600 0.291900 0.115755 +v 0.591275 0.324506 0.107145 +v 0.616800 0.363075 0.096690 +v 0.634375 0.405469 0.085312 +v 0.648200 0.449550 0.073935 +v 0.662475 0.493181 0.063480 +v 0.681400 0.534225 0.054870 +v 0.709175 0.570544 0.049027 +v 0.750000 0.600000 0.046875 +v 0.425000 0.222600 0.118800 +v 0.504193 0.239671 0.116734 +v 0.561237 0.267480 0.111125 +v 0.600732 0.303721 0.102859 +v 0.627278 0.346092 0.092822 +v 0.645475 0.392287 0.081900 +v 0.659922 0.440004 0.070978 +v 0.675218 0.486938 0.060941 +v 0.695963 0.530784 0.052675 +v 0.726757 0.569240 0.047066 +v 0.772200 0.600000 0.045000 +v 0.425000 0.194550 0.103950 +v 0.508436 0.214197 0.102142 +v 0.568254 0.245040 0.097234 +v 0.609423 0.284622 0.090002 +v 0.636907 0.330486 0.081220 +v 0.655675 0.380175 0.071662 +v 0.670693 0.431232 0.062105 +v 0.686927 0.481200 0.053323 +v 0.709346 0.527622 0.046091 +v 0.742914 0.568041 0.041183 +v 0.792600 0.600000 0.039375 +v 0.425000 0.171450 0.079200 +v 0.511930 0.193218 0.077822 +v 0.574034 0.226560 0.074083 +v 0.616579 0.268893 0.068573 +v 0.644837 0.317634 0.061882 +v 0.664075 0.370200 0.054600 +v 0.679563 0.424008 0.047318 +v 0.696571 0.476475 0.040627 +v 0.720366 0.525018 0.035117 +v 0.756220 0.567054 0.031378 +v 0.809400 0.600000 0.030000 +v 0.425000 0.155775 0.044550 +v 0.514301 0.178982 0.043775 +v 0.577955 0.214020 0.041672 +v 0.621436 0.258220 0.038572 +v 0.650218 0.308913 0.034808 +v 0.669775 0.363431 0.030712 +v 0.685582 0.419106 0.026617 +v 0.703114 0.473269 0.022853 +v 0.727845 0.523251 0.019753 +v 0.765249 0.566384 0.017650 +v 0.820800 0.600000 0.016875 +v 0.425000 0.150000 -0.000000 +v 0.515175 0.173738 -0.000000 +v 0.579400 0.209400 -0.000000 +v 0.623225 0.254288 -0.000000 +v 0.652200 0.305700 -0.000000 +v 0.671875 0.360938 -0.000000 +v 0.687800 0.417300 -0.000000 +v 0.705525 0.472088 -0.000000 +v 0.730600 0.522600 -0.000000 +v 0.768575 0.566138 -0.000000 +v 0.825000 0.600000 -0.000000 +v 0.425000 0.155775 -0.044550 +v 0.514301 0.178982 -0.043775 +v 0.577955 0.214020 -0.041672 +v 0.621436 0.258220 -0.038572 +v 0.650218 0.308913 -0.034808 +v 0.669775 0.363431 -0.030713 +v 0.685582 0.419106 -0.026617 +v 0.703114 0.473269 -0.022853 +v 0.727845 0.523251 -0.019753 +v 0.765249 0.566384 -0.017650 +v 0.820800 0.600000 -0.016875 +v 0.425000 0.171450 -0.079200 +v 0.511930 0.193218 -0.077822 +v 0.574034 0.226560 -0.074083 +v 0.616579 0.268893 -0.068573 +v 0.644837 0.317634 -0.061882 +v 0.664075 0.370200 -0.054600 +v 0.679563 0.424008 -0.047318 +v 0.696571 0.476475 -0.040627 +v 0.720366 0.525018 -0.035117 +v 0.756220 0.567054 -0.031378 +v 0.809400 0.600000 -0.030000 +v 0.425000 0.194550 -0.103950 +v 0.508436 0.214197 -0.102142 +v 0.568254 0.245040 -0.097234 +v 0.609423 0.284622 -0.090002 +v 0.636907 0.330486 -0.081220 +v 0.655675 0.380175 -0.071663 +v 0.670693 0.431232 -0.062105 +v 0.686927 0.481200 -0.053323 +v 0.709346 0.527622 -0.046091 +v 0.742914 0.568041 -0.041183 +v 0.792600 0.600000 -0.039375 +v 0.425000 0.222600 -0.118800 +v 0.504193 0.239671 -0.116734 +v 0.561237 0.267480 -0.111125 +v 0.600732 0.303721 -0.102859 +v 0.627278 0.346092 -0.092822 +v 0.645475 0.392287 -0.081900 +v 0.659922 0.440004 -0.070978 +v 0.675218 0.486938 -0.060941 +v 0.695963 0.530784 -0.052675 +v 0.726757 0.569240 -0.047066 +v 0.772200 0.600000 -0.045000 +v 0.425000 0.253125 -0.123750 +v 0.499575 0.267394 -0.121598 +v 0.553600 0.291900 -0.115755 +v 0.591275 0.324506 -0.107145 +v 0.616800 0.363075 -0.096690 +v 0.634375 0.405469 -0.085313 +v 0.648200 0.449550 -0.073935 +v 0.662475 0.493181 -0.063480 +v 0.681400 0.534225 -0.054870 +v 0.709175 0.570544 -0.049028 +v 0.750000 0.600000 -0.046875 +v 0.425000 0.283650 -0.118800 +v 0.494957 0.295116 -0.116734 +v 0.545963 0.316320 -0.111125 +v 0.581818 0.345291 -0.102859 +v 0.606322 0.380058 -0.092822 +v 0.623275 0.418650 -0.081900 +v 0.636478 0.459096 -0.070978 +v 0.649732 0.499425 -0.060941 +v 0.666837 0.537666 -0.052675 +v 0.691593 0.571848 -0.047066 +v 0.727800 0.600000 -0.045000 +v 0.425000 0.311700 -0.103950 +v 0.490714 0.320591 -0.102142 +v 0.538946 0.338760 -0.097234 +v 0.573127 0.364390 -0.090002 +v 0.596693 0.395664 -0.081220 +v 0.613075 0.430762 -0.071663 +v 0.625707 0.467868 -0.062105 +v 0.638023 0.505163 -0.053323 +v 0.653454 0.540828 -0.046091 +v 0.675436 0.573047 -0.041183 +v 0.707400 0.600000 -0.039375 +v 0.425000 0.334800 -0.079200 +v 0.487220 0.341570 -0.077822 +v 0.533166 0.357240 -0.074083 +v 0.565971 0.380120 -0.068573 +v 0.588763 0.408516 -0.061882 +v 0.604675 0.440738 -0.054600 +v 0.616837 0.475092 -0.047318 +v 0.628379 0.509888 -0.040627 +v 0.642434 0.543432 -0.035117 +v 0.662130 0.574034 -0.031378 +v 0.690600 0.600000 -0.030000 +v 0.425000 0.350475 -0.044550 +v 0.484849 0.355805 -0.043775 +v 0.529245 0.369780 -0.041672 +v 0.561114 0.390793 -0.038572 +v 0.583382 0.417237 -0.034808 +v 0.598975 0.447506 -0.030713 +v 0.610818 0.479994 -0.026617 +v 0.621836 0.513094 -0.022853 +v 0.634955 0.545199 -0.019753 +v 0.653101 0.574703 -0.017650 +v 0.679200 0.600000 -0.016875 +v 0.686852 0.605101 0.016686 +v 0.682450 0.605062 -0.000000 +v 0.694091 0.609076 0.016173 +v 0.689600 0.609000 -0.000000 +v 0.700632 0.611920 0.015417 +v 0.696150 0.611812 -0.000000 +v 0.706188 0.613632 0.014499 +v 0.701800 0.613500 -0.000000 +v 0.710472 0.614210 0.013500 +v 0.706250 0.614062 -0.000000 +v 0.713198 0.613651 0.012501 +v 0.709200 0.613500 -0.000000 +v 0.714081 0.611953 0.011583 +v 0.710350 0.611812 -0.000000 +v 0.712833 0.609113 0.010827 +v 0.709400 0.609000 -0.000000 +v 0.709168 0.605130 0.010314 +v 0.706050 0.605062 -0.000000 +v 0.702800 0.600000 0.010125 +v 0.700000 0.600000 -0.000000 +v 0.698800 0.605207 0.029664 +v 0.706282 0.609281 0.028752 +v 0.712797 0.612212 0.027408 +v 0.718097 0.613991 0.025776 +v 0.721931 0.614611 0.024000 +v 0.724051 0.614062 0.022224 +v 0.724207 0.612335 0.020592 +v 0.722150 0.609421 0.019248 +v 0.717631 0.605313 0.018336 +v 0.710400 0.600000 0.018000 +v 0.716407 0.605363 0.038934 +v 0.724246 0.609583 0.037737 +v 0.730725 0.612642 0.035973 +v 0.735647 0.614521 0.033831 +v 0.738819 0.615202 0.031500 +v 0.740045 0.614666 0.029169 +v 0.739131 0.612897 0.027027 +v 0.735882 0.609875 0.025263 +v 0.730103 0.605582 0.024066 +v 0.721600 0.600000 0.023625 +v 0.737787 0.605553 0.044496 +v 0.746061 0.609950 0.043128 +v 0.752494 0.613164 0.041112 +v 0.756958 0.615163 0.038664 +v 0.759325 0.615919 0.036000 +v 0.759466 0.615401 0.033336 +v 0.757252 0.613580 0.030888 +v 0.752555 0.610426 0.028872 +v 0.745247 0.605909 0.027504 +v 0.735200 0.600000 0.027000 +v 0.761053 0.605759 0.046350 +v 0.769800 0.610350 0.044925 +v 0.776184 0.613732 0.042825 +v 0.780150 0.615862 0.040275 +v 0.781641 0.616699 0.037500 +v 0.780600 0.616200 0.034725 +v 0.776972 0.614323 0.032175 +v 0.770700 0.611025 0.030075 +v 0.761728 0.606265 0.028650 +v 0.750000 0.600000 0.028125 +v 0.784320 0.605965 0.044496 +v 0.793539 0.610750 0.043128 +v 0.799875 0.614300 0.041112 +v 0.803342 0.616562 0.038664 +v 0.803956 0.617480 0.036000 +v 0.801734 0.616999 0.033336 +v 0.796692 0.615066 0.030888 +v 0.788845 0.611624 0.028872 +v 0.778209 0.606621 0.027504 +v 0.764800 0.600000 0.027000 +v 0.805700 0.606154 0.038934 +v 0.815354 0.611117 0.037737 +v 0.821644 0.614822 0.035973 +v 0.824653 0.617204 0.033831 +v 0.824463 0.618197 0.031500 +v 0.821155 0.617734 0.029169 +v 0.814813 0.615748 0.027027 +v 0.805518 0.612175 0.025263 +v 0.793353 0.606948 0.024066 +v 0.778400 0.600000 0.023625 +v 0.823307 0.606310 0.029664 +v 0.833318 0.611419 0.028752 +v 0.839572 0.615252 0.027408 +v 0.842203 0.617734 0.025776 +v 0.841350 0.618788 0.024000 +v 0.837149 0.618338 0.022224 +v 0.829736 0.616311 0.020592 +v 0.819250 0.612629 0.019248 +v 0.805825 0.607217 0.018336 +v 0.789600 0.600000 0.018000 +v 0.835254 0.606416 0.016686 +v 0.845509 0.611624 0.016173 +v 0.851737 0.615544 0.015417 +v 0.854112 0.618093 0.014499 +v 0.852809 0.619188 0.013500 +v 0.848002 0.618749 0.012501 +v 0.839863 0.616692 0.011583 +v 0.828567 0.612937 0.010827 +v 0.814288 0.607400 0.010314 +v 0.797200 0.600000 0.010125 +v 0.839656 0.606455 -0.000000 +v 0.850000 0.611700 -0.000000 +v 0.856219 0.615652 -0.000000 +v 0.858500 0.618225 -0.000000 +v 0.857031 0.619336 -0.000000 +v 0.852000 0.618900 -0.000000 +v 0.843594 0.616833 -0.000000 +v 0.832000 0.613050 -0.000000 +v 0.817406 0.607467 -0.000000 +v 0.800000 0.600000 -0.000000 +v 0.835254 0.606416 -0.016686 +v 0.845509 0.611624 -0.016173 +v 0.851737 0.615544 -0.015417 +v 0.854112 0.618093 -0.014499 +v 0.852809 0.619188 -0.013500 +v 0.848002 0.618749 -0.012501 +v 0.839863 0.616692 -0.011583 +v 0.828567 0.612937 -0.010827 +v 0.814288 0.607400 -0.010314 +v 0.797200 0.600000 -0.010125 +v 0.823307 0.606310 -0.029664 +v 0.833318 0.611419 -0.028752 +v 0.839572 0.615252 -0.027408 +v 0.842203 0.617734 -0.025776 +v 0.841350 0.618788 -0.024000 +v 0.837149 0.618338 -0.022224 +v 0.829736 0.616311 -0.020592 +v 0.819250 0.612629 -0.019248 +v 0.805825 0.607217 -0.018336 +v 0.789600 0.600000 -0.018000 +v 0.805700 0.606154 -0.038934 +v 0.815354 0.611117 -0.037737 +v 0.821644 0.614822 -0.035973 +v 0.824653 0.617204 -0.033831 +v 0.824463 0.618197 -0.031500 +v 0.821155 0.617734 -0.029169 +v 0.814813 0.615748 -0.027027 +v 0.805518 0.612175 -0.025263 +v 0.793353 0.606948 -0.024066 +v 0.778400 0.600000 -0.023625 +v 0.784320 0.605965 -0.044496 +v 0.793539 0.610750 -0.043128 +v 0.799875 0.614300 -0.041112 +v 0.803342 0.616562 -0.038664 +v 0.803956 0.617480 -0.036000 +v 0.801734 0.616999 -0.033336 +v 0.796692 0.615066 -0.030888 +v 0.788845 0.611624 -0.028872 +v 0.778209 0.606621 -0.027504 +v 0.764800 0.600000 -0.027000 +v 0.761053 0.605759 -0.046350 +v 0.769800 0.610350 -0.044925 +v 0.776184 0.613732 -0.042825 +v 0.780150 0.615862 -0.040275 +v 0.781641 0.616699 -0.037500 +v 0.780600 0.616200 -0.034725 +v 0.776972 0.614323 -0.032175 +v 0.770700 0.611025 -0.030075 +v 0.761728 0.606265 -0.028650 +v 0.750000 0.600000 -0.028125 +v 0.737787 0.605553 -0.044496 +v 0.746061 0.609950 -0.043128 +v 0.752494 0.613164 -0.041112 +v 0.756958 0.615163 -0.038664 +v 0.759325 0.615919 -0.036000 +v 0.759466 0.615401 -0.033336 +v 0.757252 0.613580 -0.030888 +v 0.752555 0.610426 -0.028872 +v 0.745247 0.605909 -0.027504 +v 0.735200 0.600000 -0.027000 +v 0.716407 0.605363 -0.038934 +v 0.724246 0.609583 -0.037737 +v 0.730725 0.612642 -0.035973 +v 0.735647 0.614521 -0.033831 +v 0.738819 0.615202 -0.031500 +v 0.740045 0.614666 -0.029169 +v 0.739131 0.612897 -0.027027 +v 0.735882 0.609875 -0.025263 +v 0.730103 0.605582 -0.024066 +v 0.721600 0.600000 -0.023625 +v 0.698799 0.605207 -0.029664 +v 0.706282 0.609281 -0.028752 +v 0.712797 0.612212 -0.027408 +v 0.718097 0.613991 -0.025776 +v 0.721931 0.614611 -0.024000 +v 0.724051 0.614062 -0.022224 +v 0.724207 0.612335 -0.020592 +v 0.722150 0.609421 -0.019248 +v 0.717631 0.605313 -0.018336 +v 0.710400 0.600000 -0.018000 +v 0.686852 0.605101 -0.016686 +v 0.694091 0.609076 -0.016173 +v 0.700632 0.611920 -0.015417 +v 0.706188 0.613632 -0.014499 +v 0.710472 0.614210 -0.013500 +v 0.713198 0.613651 -0.012501 +v 0.714081 0.611953 -0.011583 +v 0.712833 0.609113 -0.010827 +v 0.709168 0.605130 -0.010314 +v 0.702800 0.600000 -0.010125 +v 0.000000 0.787500 -0.000000 +v 0.048027 0.785363 0.008012 +v 0.048650 0.785363 -0.000000 +v 0.076211 0.779400 0.012714 +v 0.077200 0.779400 -0.000000 +v 0.088403 0.770288 0.014747 +v 0.089550 0.770288 -0.000000 +v 0.088452 0.758700 0.014754 +v 0.089600 0.758700 -0.000000 +v 0.080209 0.745312 0.013377 +v 0.081250 0.745312 -0.000000 +v 0.067523 0.730800 0.011258 +v 0.068400 0.730800 -0.000000 +v 0.054245 0.715837 0.009039 +v 0.054950 0.715837 -0.000000 +v 0.044224 0.701100 0.007362 +v 0.044800 0.701100 -0.000000 +v 0.041311 0.687262 0.006870 +v 0.041850 0.687262 -0.000000 +v 0.049356 0.675000 0.008204 +v 0.050000 0.675000 -0.000000 +v 0.046218 0.785363 0.015568 +v 0.073340 0.779400 0.024704 +v 0.085072 0.770288 0.028655 +v 0.085119 0.758700 0.028669 +v 0.077186 0.745312 0.025994 +v 0.064977 0.730800 0.021878 +v 0.052198 0.715837 0.017567 +v 0.042554 0.701100 0.014311 +v 0.039749 0.687262 0.013357 +v 0.047488 0.675000 0.015952 +v 0.043314 0.785363 0.022577 +v 0.068732 0.779400 0.035825 +v 0.079727 0.770288 0.041555 +v 0.079770 0.758700 0.041576 +v 0.072335 0.745312 0.037698 +v 0.060892 0.730800 0.031730 +v 0.048915 0.715837 0.025481 +v 0.039874 0.701100 0.020762 +v 0.037242 0.687262 0.019381 +v 0.044492 0.675000 0.023148 +v 0.039407 0.785363 0.028947 +v 0.062532 0.779400 0.045933 +v 0.072535 0.770288 0.053281 +v 0.072574 0.758700 0.053308 +v 0.065808 0.745312 0.048337 +v 0.055396 0.730800 0.040686 +v 0.044497 0.715837 0.032677 +v 0.036270 0.701100 0.026628 +v 0.033872 0.687262 0.024861 +v 0.040464 0.675000 0.029696 +v 0.034587 0.785363 0.034587 +v 0.054884 0.779400 0.054884 +v 0.063663 0.770288 0.063663 +v 0.063697 0.758700 0.063697 +v 0.057758 0.745312 0.057758 +v 0.048618 0.730800 0.048618 +v 0.039050 0.715837 0.039050 +v 0.031826 0.701100 0.031826 +v 0.029719 0.687262 0.029718 +v 0.035500 0.675000 0.035500 +v 0.028947 0.785363 0.039406 +v 0.045934 0.779400 0.062532 +v 0.053281 0.770288 0.072534 +v 0.053308 0.758700 0.072574 +v 0.048337 0.745312 0.065808 +v 0.040686 0.730800 0.055396 +v 0.032677 0.715837 0.044497 +v 0.026628 0.701100 0.036269 +v 0.024861 0.687262 0.033872 +v 0.029696 0.675000 0.040464 +v 0.022577 0.785363 0.043314 +v 0.035825 0.779400 0.068732 +v 0.041555 0.770288 0.079727 +v 0.041577 0.758700 0.079770 +v 0.037698 0.745312 0.072335 +v 0.031730 0.730800 0.060892 +v 0.025481 0.715837 0.048914 +v 0.020762 0.701100 0.039874 +v 0.019381 0.687262 0.037242 +v 0.023148 0.675000 0.044492 +v 0.015568 0.785363 0.046217 +v 0.024704 0.779400 0.073340 +v 0.028655 0.770288 0.085072 +v 0.028669 0.758700 0.085119 +v 0.025994 0.745312 0.077186 +v 0.021878 0.730800 0.064977 +v 0.017568 0.715837 0.052198 +v 0.014311 0.701100 0.042554 +v 0.013357 0.687262 0.039749 +v 0.015952 0.675000 0.047488 +v 0.008012 0.785363 0.048027 +v 0.012714 0.779400 0.076211 +v 0.014747 0.770288 0.088402 +v 0.014754 0.758700 0.088452 +v 0.013377 0.745312 0.080208 +v 0.011258 0.730800 0.067523 +v 0.009039 0.715837 0.054245 +v 0.007362 0.701100 0.044224 +v 0.006870 0.687262 0.041311 +v 0.008204 0.675000 0.049356 +v -0.000000 0.785363 0.048650 +v -0.000000 0.779400 0.077200 +v -0.000000 0.770288 0.089550 +v -0.000000 0.758700 0.089600 +v -0.000000 0.745312 0.081250 +v -0.000000 0.730800 0.068400 +v -0.000000 0.715837 0.054950 +v -0.000000 0.701100 0.044800 +v -0.000000 0.687262 0.041850 +v -0.000000 0.675000 0.050000 +v -0.008012 0.785363 0.048027 +v -0.012714 0.779400 0.076211 +v -0.014747 0.770288 0.088402 +v -0.014754 0.758700 0.088452 +v -0.013377 0.745312 0.080208 +v -0.011258 0.730800 0.067523 +v -0.009039 0.715837 0.054245 +v -0.007362 0.701100 0.044224 +v -0.006870 0.687262 0.041311 +v -0.008204 0.675000 0.049356 +v -0.015568 0.785363 0.046217 +v -0.024704 0.779400 0.073340 +v -0.028655 0.770288 0.085072 +v -0.028669 0.758700 0.085119 +v -0.025994 0.745312 0.077186 +v -0.021878 0.730800 0.064977 +v -0.017568 0.715837 0.052198 +v -0.014311 0.701100 0.042554 +v -0.013357 0.687262 0.039749 +v -0.015952 0.675000 0.047488 +v -0.022577 0.785363 0.043314 +v -0.035825 0.779400 0.068732 +v -0.041555 0.770288 0.079727 +v -0.041577 0.758700 0.079770 +v -0.037698 0.745312 0.072335 +v -0.031730 0.730800 0.060892 +v -0.025481 0.715837 0.048914 +v -0.020762 0.701100 0.039874 +v -0.019381 0.687262 0.037242 +v -0.023148 0.675000 0.044492 +v -0.028947 0.785363 0.039406 +v -0.045934 0.779400 0.062532 +v -0.053281 0.770288 0.072534 +v -0.053308 0.758700 0.072574 +v -0.048337 0.745312 0.065808 +v -0.040686 0.730800 0.055396 +v -0.032677 0.715837 0.044497 +v -0.026628 0.701100 0.036269 +v -0.024861 0.687262 0.033872 +v -0.029696 0.675000 0.040464 +v -0.034587 0.785363 0.034587 +v -0.054884 0.779400 0.054884 +v -0.063663 0.770288 0.063663 +v -0.063697 0.758700 0.063697 +v -0.057758 0.745312 0.057758 +v -0.048618 0.730800 0.048618 +v -0.039050 0.715837 0.039050 +v -0.031826 0.701100 0.031826 +v -0.029719 0.687262 0.029718 +v -0.035500 0.675000 0.035500 +v -0.039407 0.785363 0.028947 +v -0.062532 0.779400 0.045933 +v -0.072535 0.770288 0.053281 +v -0.072574 0.758700 0.053308 +v -0.065808 0.745312 0.048337 +v -0.055396 0.730800 0.040686 +v -0.044497 0.715837 0.032677 +v -0.036270 0.701100 0.026628 +v -0.033872 0.687262 0.024861 +v -0.040464 0.675000 0.029696 +v -0.043314 0.785363 0.022576 +v -0.068732 0.779400 0.035825 +v -0.079727 0.770288 0.041555 +v -0.079770 0.758700 0.041576 +v -0.072335 0.745312 0.037698 +v -0.060892 0.730800 0.031730 +v -0.048915 0.715837 0.025481 +v -0.039874 0.701100 0.020762 +v -0.037242 0.687262 0.019381 +v -0.044492 0.675000 0.023148 +v -0.046218 0.785363 0.015568 +v -0.073340 0.779400 0.024704 +v -0.085072 0.770288 0.028655 +v -0.085119 0.758700 0.028669 +v -0.077186 0.745312 0.025994 +v -0.064977 0.730800 0.021878 +v -0.052198 0.715837 0.017567 +v -0.042554 0.701100 0.014311 +v -0.039749 0.687262 0.013357 +v -0.047488 0.675000 0.015952 +v -0.048027 0.785363 0.008012 +v -0.076211 0.779400 0.012714 +v -0.088403 0.770288 0.014747 +v -0.088452 0.758700 0.014754 +v -0.080209 0.745312 0.013377 +v -0.067523 0.730800 0.011258 +v -0.054245 0.715837 0.009039 +v -0.044224 0.701100 0.007362 +v -0.041311 0.687262 0.006870 +v -0.049356 0.675000 0.008204 +v -0.048650 0.785363 -0.000000 +v -0.077200 0.779400 -0.000000 +v -0.089550 0.770288 -0.000000 +v -0.089600 0.758700 -0.000000 +v -0.081250 0.745312 -0.000000 +v -0.068400 0.730800 -0.000000 +v -0.054950 0.715837 -0.000000 +v -0.044800 0.701100 -0.000000 +v -0.041850 0.687262 -0.000000 +v -0.050000 0.675000 -0.000000 +v -0.048027 0.785363 -0.008012 +v -0.076211 0.779400 -0.012714 +v -0.088403 0.770288 -0.014747 +v -0.088452 0.758700 -0.014754 +v -0.080209 0.745312 -0.013377 +v -0.067523 0.730800 -0.011258 +v -0.054245 0.715837 -0.009039 +v -0.044224 0.701100 -0.007363 +v -0.041311 0.687262 -0.006870 +v -0.049356 0.675000 -0.008204 +v -0.046218 0.785363 -0.015568 +v -0.073340 0.779400 -0.024704 +v -0.085072 0.770288 -0.028655 +v -0.085119 0.758700 -0.028669 +v -0.077186 0.745312 -0.025994 +v -0.064977 0.730800 -0.021878 +v -0.052198 0.715837 -0.017568 +v -0.042554 0.701100 -0.014312 +v -0.039749 0.687262 -0.013357 +v -0.047488 0.675000 -0.015952 +v -0.043314 0.785363 -0.022577 +v -0.068732 0.779400 -0.035825 +v -0.079727 0.770288 -0.041555 +v -0.079770 0.758700 -0.041577 +v -0.072335 0.745312 -0.037698 +v -0.060892 0.730800 -0.031730 +v -0.048915 0.715837 -0.025481 +v -0.039874 0.701100 -0.020762 +v -0.037242 0.687262 -0.019381 +v -0.044492 0.675000 -0.023148 +v -0.039407 0.785363 -0.028947 +v -0.062532 0.779400 -0.045934 +v -0.072535 0.770288 -0.053281 +v -0.072574 0.758700 -0.053309 +v -0.065808 0.745312 -0.048337 +v -0.055396 0.730800 -0.040686 +v -0.044497 0.715837 -0.032677 +v -0.036270 0.701100 -0.026628 +v -0.033872 0.687262 -0.024861 +v -0.040464 0.675000 -0.029696 +v -0.034587 0.785363 -0.034587 +v -0.054884 0.779400 -0.054884 +v -0.063663 0.770288 -0.063663 +v -0.063697 0.758700 -0.063697 +v -0.057758 0.745312 -0.057758 +v -0.048618 0.730800 -0.048618 +v -0.039050 0.715837 -0.039050 +v -0.031826 0.701100 -0.031826 +v -0.029719 0.687262 -0.029719 +v -0.035500 0.675000 -0.035500 +v -0.028947 0.785363 -0.039407 +v -0.045934 0.779400 -0.062532 +v -0.053281 0.770288 -0.072535 +v -0.053308 0.758700 -0.072574 +v -0.048337 0.745312 -0.065808 +v -0.040686 0.730800 -0.055396 +v -0.032677 0.715837 -0.044497 +v -0.026628 0.701100 -0.036270 +v -0.024861 0.687262 -0.033872 +v -0.029696 0.675000 -0.040464 +v -0.022577 0.785363 -0.043314 +v -0.035825 0.779400 -0.068732 +v -0.041555 0.770288 -0.079727 +v -0.041577 0.758700 -0.079771 +v -0.037698 0.745312 -0.072335 +v -0.031730 0.730800 -0.060892 +v -0.025481 0.715837 -0.048915 +v -0.020762 0.701100 -0.039874 +v -0.019381 0.687262 -0.037242 +v -0.023148 0.675000 -0.044492 +v -0.015568 0.785363 -0.046218 +v -0.024704 0.779400 -0.073340 +v -0.028655 0.770288 -0.085072 +v -0.028669 0.758700 -0.085119 +v -0.025994 0.745312 -0.077186 +v -0.021878 0.730800 -0.064977 +v -0.017568 0.715837 -0.052198 +v -0.014311 0.701100 -0.042554 +v -0.013357 0.687262 -0.039749 +v -0.015952 0.675000 -0.047488 +v -0.008012 0.785363 -0.048027 +v -0.012714 0.779400 -0.076211 +v -0.014747 0.770288 -0.088403 +v -0.014754 0.758700 -0.088452 +v -0.013377 0.745312 -0.080209 +v -0.011258 0.730800 -0.067523 +v -0.009039 0.715837 -0.054245 +v -0.007362 0.701100 -0.044224 +v -0.006870 0.687262 -0.041311 +v -0.008204 0.675000 -0.049356 +v 0.000000 0.785363 -0.048650 +v 0.000000 0.779400 -0.077200 +v 0.000000 0.770288 -0.089550 +v 0.000000 0.758700 -0.089600 +v 0.000000 0.745312 -0.081250 +v 0.000000 0.730800 -0.068400 +v 0.000000 0.715837 -0.054950 +v 0.000000 0.701100 -0.044800 +v 0.000000 0.687262 -0.041850 +v 0.000000 0.675000 -0.050000 +v 0.008012 0.785363 -0.048027 +v 0.012714 0.779400 -0.076211 +v 0.014747 0.770288 -0.088403 +v 0.014754 0.758700 -0.088452 +v 0.013377 0.745312 -0.080209 +v 0.011258 0.730800 -0.067523 +v 0.009039 0.715837 -0.054245 +v 0.007362 0.701100 -0.044224 +v 0.006870 0.687262 -0.041311 +v 0.008204 0.675000 -0.049356 +v 0.015568 0.785363 -0.046218 +v 0.024704 0.779400 -0.073340 +v 0.028655 0.770288 -0.085072 +v 0.028669 0.758700 -0.085119 +v 0.025994 0.745312 -0.077186 +v 0.021878 0.730800 -0.064977 +v 0.017568 0.715837 -0.052198 +v 0.014311 0.701100 -0.042554 +v 0.013357 0.687262 -0.039749 +v 0.015952 0.675000 -0.047488 +v 0.022577 0.785363 -0.043314 +v 0.035825 0.779400 -0.068732 +v 0.041555 0.770288 -0.079727 +v 0.041577 0.758700 -0.079771 +v 0.037698 0.745312 -0.072335 +v 0.031730 0.730800 -0.060892 +v 0.025481 0.715837 -0.048915 +v 0.020762 0.701100 -0.039874 +v 0.019381 0.687262 -0.037242 +v 0.023148 0.675000 -0.044492 +v 0.028947 0.785363 -0.039407 +v 0.045934 0.779400 -0.062532 +v 0.053281 0.770288 -0.072535 +v 0.053308 0.758700 -0.072574 +v 0.048337 0.745312 -0.065808 +v 0.040686 0.730800 -0.055396 +v 0.032677 0.715837 -0.044497 +v 0.026628 0.701100 -0.036270 +v 0.024861 0.687262 -0.033872 +v 0.029696 0.675000 -0.040464 +v 0.034587 0.785363 -0.034587 +v 0.054884 0.779400 -0.054884 +v 0.063663 0.770288 -0.063663 +v 0.063697 0.758700 -0.063697 +v 0.057758 0.745312 -0.057758 +v 0.048618 0.730800 -0.048618 +v 0.039050 0.715837 -0.039050 +v 0.031826 0.701100 -0.031826 +v 0.029719 0.687262 -0.029719 +v 0.035500 0.675000 -0.035500 +v 0.039407 0.785363 -0.028947 +v 0.062532 0.779400 -0.045934 +v 0.072535 0.770288 -0.053281 +v 0.072574 0.758700 -0.053309 +v 0.065808 0.745312 -0.048337 +v 0.055396 0.730800 -0.040686 +v 0.044497 0.715837 -0.032677 +v 0.036270 0.701100 -0.026628 +v 0.033872 0.687262 -0.024861 +v 0.040464 0.675000 -0.029696 +v 0.043314 0.785363 -0.022577 +v 0.068732 0.779400 -0.035825 +v 0.079727 0.770288 -0.041555 +v 0.079770 0.758700 -0.041577 +v 0.072335 0.745312 -0.037698 +v 0.060892 0.730800 -0.031730 +v 0.048915 0.715837 -0.025481 +v 0.039874 0.701100 -0.020762 +v 0.037242 0.687262 -0.019381 +v 0.044492 0.675000 -0.023148 +v 0.046218 0.785363 -0.015568 +v 0.073340 0.779400 -0.024704 +v 0.085072 0.770288 -0.028655 +v 0.085119 0.758700 -0.028669 +v 0.077186 0.745312 -0.025994 +v 0.064977 0.730800 -0.021878 +v 0.052198 0.715837 -0.017568 +v 0.042554 0.701100 -0.014311 +v 0.039749 0.687262 -0.013357 +v 0.047488 0.675000 -0.015952 +v 0.048027 0.785363 -0.008012 +v 0.076211 0.779400 -0.012714 +v 0.088403 0.770288 -0.014747 +v 0.088452 0.758700 -0.014754 +v 0.080209 0.745312 -0.013377 +v 0.067523 0.730800 -0.011258 +v 0.054245 0.715837 -0.009039 +v 0.044224 0.701100 -0.007363 +v 0.041311 0.687262 -0.006870 +v 0.049356 0.675000 -0.008204 +v 0.068950 0.664800 0.011461 +v 0.069850 0.664800 -0.000000 +v 0.096540 0.656400 0.016047 +v 0.097800 0.656400 -0.000000 +v 0.129757 0.649350 0.021568 +v 0.131450 0.649350 -0.000000 +v 0.166231 0.643200 0.027631 +v 0.168400 0.643200 -0.000000 +v 0.203593 0.637500 0.033841 +v 0.206250 0.637500 -0.000000 +v 0.239475 0.631800 0.039806 +v 0.242600 0.631800 -0.000000 +v 0.271507 0.625650 0.045130 +v 0.275050 0.625650 -0.000000 +v 0.297321 0.618600 0.049421 +v 0.301200 0.618600 -0.000000 +v 0.314546 0.610200 0.052284 +v 0.318650 0.610200 -0.000000 +v 0.320814 0.600000 0.053326 +v 0.325000 0.600000 -0.000000 +v 0.066341 0.664800 0.022285 +v 0.092887 0.656400 0.031202 +v 0.124846 0.649350 0.041938 +v 0.159940 0.643200 0.053726 +v 0.195888 0.637500 0.065802 +v 0.230412 0.631800 0.077399 +v 0.261231 0.625650 0.087752 +v 0.286068 0.618600 0.096095 +v 0.302641 0.610200 0.101662 +v 0.308672 0.600000 0.103688 +v 0.062155 0.664800 0.032338 +v 0.087026 0.656400 0.045277 +v 0.116969 0.649350 0.060856 +v 0.149849 0.643200 0.077962 +v 0.183529 0.637500 0.095485 +v 0.215875 0.631800 0.112314 +v 0.244750 0.625650 0.127337 +v 0.268020 0.618600 0.139444 +v 0.283548 0.610200 0.147522 +v 0.289198 0.600000 0.150462 +v 0.056528 0.664800 0.041485 +v 0.079148 0.656400 0.058085 +v 0.106380 0.649350 0.078071 +v 0.136283 0.643200 0.100016 +v 0.166914 0.637500 0.122496 +v 0.196331 0.631800 0.144085 +v 0.222592 0.625650 0.163358 +v 0.243755 0.618600 0.178889 +v 0.257877 0.610200 0.189253 +v 0.263016 0.600000 0.193024 +v 0.049594 0.664800 0.049593 +v 0.069438 0.656400 0.069438 +v 0.093330 0.649350 0.093329 +v 0.119564 0.643200 0.119564 +v 0.146438 0.637500 0.146437 +v 0.172246 0.631800 0.172246 +v 0.195285 0.625650 0.195285 +v 0.213852 0.618600 0.213852 +v 0.226241 0.610200 0.226241 +v 0.230750 0.600000 0.230750 +v 0.041485 0.664800 0.056528 +v 0.058085 0.656400 0.079148 +v 0.078071 0.649350 0.106380 +v 0.100016 0.643200 0.136283 +v 0.122496 0.637500 0.166914 +v 0.144085 0.631800 0.196331 +v 0.163358 0.625650 0.222592 +v 0.178889 0.618600 0.243755 +v 0.189253 0.610200 0.257877 +v 0.193024 0.600000 0.263016 +v 0.032338 0.664800 0.062155 +v 0.045278 0.656400 0.087026 +v 0.060856 0.649350 0.116969 +v 0.077963 0.643200 0.149849 +v 0.095486 0.637500 0.183529 +v 0.112314 0.631800 0.215875 +v 0.127337 0.625650 0.244750 +v 0.139444 0.618600 0.268020 +v 0.147522 0.610200 0.283548 +v 0.150462 0.600000 0.289198 +v 0.022285 0.664800 0.066341 +v 0.031202 0.656400 0.092886 +v 0.041938 0.649350 0.124846 +v 0.053726 0.643200 0.159940 +v 0.065802 0.637500 0.195888 +v 0.077399 0.631800 0.230412 +v 0.087752 0.625650 0.261231 +v 0.096095 0.618600 0.286068 +v 0.101662 0.610200 0.302641 +v 0.103688 0.600000 0.308672 +v 0.011461 0.664800 0.068950 +v 0.016047 0.656400 0.096540 +v 0.021568 0.649350 0.129757 +v 0.027631 0.643200 0.166231 +v 0.033842 0.637500 0.203593 +v 0.039806 0.631800 0.239475 +v 0.045130 0.625650 0.271507 +v 0.049421 0.618600 0.297321 +v 0.052284 0.610200 0.314546 +v 0.053326 0.600000 0.320814 +v -0.000000 0.664800 0.069850 +v -0.000000 0.656400 0.097800 +v -0.000000 0.649350 0.131450 +v -0.000000 0.643200 0.168400 +v -0.000000 0.637500 0.206250 +v -0.000000 0.631800 0.242600 +v -0.000000 0.625650 0.275050 +v -0.000000 0.618600 0.301200 +v -0.000000 0.610200 0.318650 +v -0.000000 0.600000 0.325000 +v -0.011461 0.664800 0.068950 +v -0.016047 0.656400 0.096540 +v -0.021568 0.649350 0.129757 +v -0.027631 0.643200 0.166231 +v -0.033842 0.637500 0.203593 +v -0.039806 0.631800 0.239475 +v -0.045130 0.625650 0.271507 +v -0.049421 0.618600 0.297321 +v -0.052284 0.610200 0.314546 +v -0.053326 0.600000 0.320814 +v -0.022285 0.664800 0.066341 +v -0.031202 0.656400 0.092886 +v -0.041938 0.649350 0.124846 +v -0.053726 0.643200 0.159940 +v -0.065802 0.637500 0.195888 +v -0.077399 0.631800 0.230412 +v -0.087752 0.625650 0.261231 +v -0.096095 0.618600 0.286068 +v -0.101662 0.610200 0.302641 +v -0.103688 0.600000 0.308672 +v -0.032338 0.664800 0.062155 +v -0.045278 0.656400 0.087026 +v -0.060856 0.649350 0.116969 +v -0.077963 0.643200 0.149849 +v -0.095486 0.637500 0.183529 +v -0.112314 0.631800 0.215875 +v -0.127337 0.625650 0.244750 +v -0.139444 0.618600 0.268020 +v -0.147522 0.610200 0.283547 +v -0.150462 0.600000 0.289198 +v -0.041485 0.664800 0.056528 +v -0.058085 0.656400 0.079148 +v -0.078071 0.649350 0.106380 +v -0.100016 0.643200 0.136283 +v -0.122496 0.637500 0.166914 +v -0.144085 0.631800 0.196331 +v -0.163358 0.625650 0.222592 +v -0.178889 0.618600 0.243755 +v -0.189253 0.610200 0.257877 +v -0.193024 0.600000 0.263016 +v -0.049594 0.664800 0.049593 +v -0.069438 0.656400 0.069438 +v -0.093330 0.649350 0.093329 +v -0.119564 0.643200 0.119564 +v -0.146438 0.637500 0.146437 +v -0.172246 0.631800 0.172246 +v -0.195285 0.625650 0.195285 +v -0.213852 0.618600 0.213852 +v -0.226241 0.610200 0.226241 +v -0.230750 0.600000 0.230750 +v -0.056528 0.664800 0.041485 +v -0.079148 0.656400 0.058085 +v -0.106380 0.649350 0.078071 +v -0.136283 0.643200 0.100016 +v -0.166914 0.637500 0.122496 +v -0.196331 0.631800 0.144085 +v -0.222592 0.625650 0.163358 +v -0.243755 0.618600 0.178889 +v -0.257877 0.610200 0.189253 +v -0.263016 0.600000 0.193024 +v -0.062155 0.664800 0.032338 +v -0.087026 0.656400 0.045277 +v -0.116969 0.649350 0.060856 +v -0.149849 0.643200 0.077962 +v -0.183530 0.637500 0.095485 +v -0.215875 0.631800 0.112314 +v -0.244751 0.625650 0.127337 +v -0.268020 0.618600 0.139444 +v -0.283548 0.610200 0.147522 +v -0.289198 0.600000 0.150462 +v -0.066341 0.664800 0.022285 +v -0.092887 0.656400 0.031202 +v -0.124846 0.649350 0.041938 +v -0.159940 0.643200 0.053726 +v -0.195888 0.637500 0.065802 +v -0.230412 0.631800 0.077399 +v -0.261232 0.625650 0.087752 +v -0.286068 0.618600 0.096095 +v -0.302641 0.610200 0.101662 +v -0.308672 0.600000 0.103688 +v -0.068950 0.664800 0.011461 +v -0.096540 0.656400 0.016047 +v -0.129757 0.649350 0.021568 +v -0.166231 0.643200 0.027631 +v -0.203593 0.637500 0.033841 +v -0.239475 0.631800 0.039806 +v -0.271507 0.625650 0.045130 +v -0.297321 0.618600 0.049421 +v -0.314546 0.610200 0.052284 +v -0.320814 0.600000 0.053326 +v -0.069850 0.664800 -0.000000 +v -0.097800 0.656400 -0.000000 +v -0.131450 0.649350 -0.000000 +v -0.168400 0.643200 -0.000000 +v -0.206250 0.637500 -0.000000 +v -0.242600 0.631800 -0.000000 +v -0.275050 0.625650 -0.000000 +v -0.301200 0.618600 -0.000000 +v -0.318650 0.610200 -0.000000 +v -0.325000 0.600000 -0.000000 +v -0.068950 0.664800 -0.011461 +v -0.096540 0.656400 -0.016047 +v -0.129757 0.649350 -0.021568 +v -0.166231 0.643200 -0.027631 +v -0.203593 0.637500 -0.033842 +v -0.239475 0.631800 -0.039806 +v -0.271507 0.625650 -0.045130 +v -0.297321 0.618600 -0.049421 +v -0.314546 0.610200 -0.052284 +v -0.320814 0.600000 -0.053326 +v -0.066341 0.664800 -0.022285 +v -0.092887 0.656400 -0.031202 +v -0.124846 0.649350 -0.041938 +v -0.159940 0.643200 -0.053726 +v -0.195888 0.637500 -0.065802 +v -0.230412 0.631800 -0.077399 +v -0.261231 0.625650 -0.087752 +v -0.286068 0.618600 -0.096095 +v -0.302641 0.610200 -0.101662 +v -0.308672 0.600000 -0.103688 +v -0.062155 0.664800 -0.032338 +v -0.087026 0.656400 -0.045278 +v -0.116969 0.649350 -0.060856 +v -0.149849 0.643200 -0.077963 +v -0.183529 0.637500 -0.095486 +v -0.215875 0.631800 -0.112314 +v -0.244750 0.625650 -0.127337 +v -0.268020 0.618600 -0.139444 +v -0.283548 0.610200 -0.147522 +v -0.289198 0.600000 -0.150462 +v -0.056528 0.664800 -0.041485 +v -0.079148 0.656400 -0.058085 +v -0.106380 0.649350 -0.078071 +v -0.136283 0.643200 -0.100016 +v -0.166914 0.637500 -0.122496 +v -0.196331 0.631800 -0.144085 +v -0.222592 0.625650 -0.163358 +v -0.243755 0.618600 -0.178889 +v -0.257877 0.610200 -0.189253 +v -0.263016 0.600000 -0.193024 +v -0.049594 0.664800 -0.049594 +v -0.069438 0.656400 -0.069438 +v -0.093330 0.649350 -0.093330 +v -0.119564 0.643200 -0.119564 +v -0.146438 0.637500 -0.146438 +v -0.172246 0.631800 -0.172246 +v -0.195285 0.625650 -0.195286 +v -0.213852 0.618600 -0.213852 +v -0.226241 0.610200 -0.226242 +v -0.230750 0.600000 -0.230750 +v -0.041485 0.664800 -0.056528 +v -0.058085 0.656400 -0.079148 +v -0.078071 0.649350 -0.106380 +v -0.100016 0.643200 -0.136283 +v -0.122496 0.637500 -0.166914 +v -0.144085 0.631800 -0.196331 +v -0.163358 0.625650 -0.222592 +v -0.178889 0.618600 -0.243755 +v -0.189253 0.610200 -0.257877 +v -0.193024 0.600000 -0.263016 +v -0.032338 0.664800 -0.062155 +v -0.045278 0.656400 -0.087026 +v -0.060856 0.649350 -0.116970 +v -0.077963 0.643200 -0.149849 +v -0.095486 0.637500 -0.183530 +v -0.112314 0.631800 -0.215875 +v -0.127337 0.625650 -0.244751 +v -0.139444 0.618600 -0.268020 +v -0.147522 0.610200 -0.283548 +v -0.150462 0.600000 -0.289198 +v -0.022285 0.664800 -0.066341 +v -0.031202 0.656400 -0.092887 +v -0.041938 0.649350 -0.124846 +v -0.053726 0.643200 -0.159940 +v -0.065802 0.637500 -0.195888 +v -0.077399 0.631800 -0.230412 +v -0.087752 0.625650 -0.261232 +v -0.096095 0.618600 -0.286068 +v -0.101662 0.610200 -0.302641 +v -0.103688 0.600000 -0.308672 +v -0.011461 0.664800 -0.068950 +v -0.016047 0.656400 -0.096540 +v -0.021568 0.649350 -0.129757 +v -0.027631 0.643200 -0.166231 +v -0.033842 0.637500 -0.203594 +v -0.039806 0.631800 -0.239475 +v -0.045130 0.625650 -0.271507 +v -0.049421 0.618600 -0.297321 +v -0.052284 0.610200 -0.314546 +v -0.053326 0.600000 -0.320814 +v 0.000000 0.664800 -0.069850 +v 0.000000 0.656400 -0.097800 +v 0.000000 0.649350 -0.131450 +v 0.000000 0.643200 -0.168400 +v 0.000000 0.637500 -0.206250 +v 0.000000 0.631800 -0.242600 +v 0.000000 0.625650 -0.275050 +v 0.000000 0.618600 -0.301200 +v 0.000000 0.610200 -0.318650 +v 0.000000 0.600000 -0.325000 +v 0.011461 0.664800 -0.068950 +v 0.016047 0.656400 -0.096540 +v 0.021568 0.649350 -0.129757 +v 0.027631 0.643200 -0.166231 +v 0.033842 0.637500 -0.203594 +v 0.039806 0.631800 -0.239475 +v 0.045130 0.625650 -0.271507 +v 0.049421 0.618600 -0.297321 +v 0.052284 0.610200 -0.314546 +v 0.053326 0.600000 -0.320814 +v 0.022285 0.664800 -0.066341 +v 0.031202 0.656400 -0.092887 +v 0.041938 0.649350 -0.124846 +v 0.053726 0.643200 -0.159940 +v 0.065802 0.637500 -0.195888 +v 0.077399 0.631800 -0.230412 +v 0.087752 0.625650 -0.261231 +v 0.096095 0.618600 -0.286068 +v 0.101662 0.610200 -0.302641 +v 0.103688 0.600000 -0.308672 +v 0.032338 0.664800 -0.062155 +v 0.045278 0.656400 -0.087026 +v 0.060856 0.649350 -0.116969 +v 0.077963 0.643200 -0.149849 +v 0.095486 0.637500 -0.183530 +v 0.112314 0.631800 -0.215875 +v 0.127337 0.625650 -0.244750 +v 0.139444 0.618600 -0.268020 +v 0.147522 0.610200 -0.283548 +v 0.150462 0.600000 -0.289198 +v 0.041485 0.664800 -0.056528 +v 0.058085 0.656400 -0.079148 +v 0.078071 0.649350 -0.106380 +v 0.100016 0.643200 -0.136283 +v 0.122496 0.637500 -0.166914 +v 0.144085 0.631800 -0.196331 +v 0.163358 0.625650 -0.222592 +v 0.178889 0.618600 -0.243755 +v 0.189253 0.610200 -0.257877 +v 0.193024 0.600000 -0.263016 +v 0.049594 0.664800 -0.049594 +v 0.069438 0.656400 -0.069438 +v 0.093330 0.649350 -0.093330 +v 0.119564 0.643200 -0.119564 +v 0.146438 0.637500 -0.146438 +v 0.172246 0.631800 -0.172246 +v 0.195285 0.625650 -0.195286 +v 0.213852 0.618600 -0.213852 +v 0.226241 0.610200 -0.226242 +v 0.230750 0.600000 -0.230750 +v 0.056528 0.664800 -0.041485 +v 0.079148 0.656400 -0.058085 +v 0.106380 0.649350 -0.078071 +v 0.136283 0.643200 -0.100016 +v 0.166914 0.637500 -0.122496 +v 0.196331 0.631800 -0.144085 +v 0.222592 0.625650 -0.163358 +v 0.243755 0.618600 -0.178889 +v 0.257877 0.610200 -0.189253 +v 0.263016 0.600000 -0.193024 +v 0.062155 0.664800 -0.032338 +v 0.087026 0.656400 -0.045278 +v 0.116969 0.649350 -0.060856 +v 0.149849 0.643200 -0.077963 +v 0.183530 0.637500 -0.095486 +v 0.215875 0.631800 -0.112314 +v 0.244751 0.625650 -0.127337 +v 0.268020 0.618600 -0.139444 +v 0.283548 0.610200 -0.147522 +v 0.289198 0.600000 -0.150462 +v 0.066341 0.664800 -0.022285 +v 0.092887 0.656400 -0.031202 +v 0.124846 0.649350 -0.041938 +v 0.159940 0.643200 -0.053726 +v 0.195888 0.637500 -0.065802 +v 0.230412 0.631800 -0.077399 +v 0.261232 0.625650 -0.087752 +v 0.286068 0.618600 -0.096095 +v 0.302641 0.610200 -0.101662 +v 0.308672 0.600000 -0.103688 +v 0.068950 0.664800 -0.011461 +v 0.096540 0.656400 -0.016047 +v 0.129757 0.649350 -0.021568 +v 0.166231 0.643200 -0.027631 +v 0.203593 0.637500 -0.033842 +v 0.239475 0.631800 -0.039806 +v 0.271507 0.625650 -0.045130 +v 0.297321 0.618600 -0.049421 +v 0.314546 0.610200 -0.052284 +v 0.320814 0.600000 -0.053326 +vt 0.684148 0.500000 +vt 0.681346 0.442629 +vt 0.679187 0.443206 +vt 0.681961 0.500000 +vt 0.678340 0.443432 +vt 0.681102 0.500000 +vt 0.678620 0.443358 +vt 0.681386 0.500000 +vt 0.679843 0.443031 +vt 0.682625 0.500000 +vt 0.681825 0.442501 +vt 0.684633 0.500000 +vt 0.684383 0.441818 +vt 0.687224 0.500000 +vt 0.687331 0.441030 +vt 0.690211 0.500000 +vt 0.690487 0.440187 +vt 0.693408 0.500000 +vt 0.693666 0.439338 +vt 0.696628 0.500000 +vt 0.696683 0.438531 +vt 0.699685 0.500000 +vt 0.673219 0.388447 +vt 0.671142 0.389569 +vt 0.670327 0.390009 +vt 0.670596 0.389864 +vt 0.671773 0.389228 +vt 0.673680 0.388198 +vt 0.676141 0.386870 +vt 0.678978 0.385338 +vt 0.682014 0.383699 +vt 0.685073 0.382047 +vt 0.687976 0.380479 +vt 0.660185 0.338126 +vt 0.658239 0.339753 +vt 0.657475 0.340392 +vt 0.657728 0.340181 +vt 0.658830 0.339259 +vt 0.660617 0.337765 +vt 0.662923 0.335836 +vt 0.665581 0.333614 +vt 0.668425 0.331235 +vt 0.671291 0.328838 +vt 0.674011 0.326563 +vt 0.642661 0.292336 +vt 0.640892 0.294423 +vt 0.640197 0.295243 +vt 0.640426 0.294972 +vt 0.641429 0.293789 +vt 0.643054 0.291872 +vt 0.645151 0.289399 +vt 0.647568 0.286547 +vt 0.650156 0.283495 +vt 0.652762 0.280421 +vt 0.655236 0.277502 +vt 0.621066 0.251748 +vt 0.619513 0.254244 +vt 0.618903 0.255224 +vt 0.619105 0.254900 +vt 0.619984 0.253486 +vt 0.621410 0.251194 +vt 0.623250 0.248237 +vt 0.625371 0.244828 +vt 0.627640 0.241180 +vt 0.629927 0.237505 +vt 0.632097 0.234016 +vt 0.595815 0.217035 +vt 0.594516 0.219880 +vt 0.594007 0.220996 +vt 0.594175 0.220628 +vt 0.594911 0.219016 +vt 0.596104 0.216403 +vt 0.597642 0.213033 +vt 0.599416 0.209147 +vt 0.601315 0.204989 +vt 0.603228 0.200800 +vt 0.605043 0.196823 +vt 0.567328 0.188867 +vt 0.566316 0.191995 +vt 0.565918 0.193223 +vt 0.566050 0.192817 +vt 0.566623 0.191045 +vt 0.567553 0.188173 +vt 0.568753 0.184467 +vt 0.570135 0.180194 +vt 0.571615 0.175622 +vt 0.573106 0.171016 +vt 0.574521 0.166643 +vt 0.536022 0.167916 +vt 0.535324 0.171255 +vt 0.535051 0.172565 +vt 0.535141 0.172132 +vt 0.535536 0.170241 +vt 0.536177 0.167175 +vt 0.537004 0.163219 +vt 0.537957 0.158659 +vt 0.538977 0.153779 +vt 0.540004 0.148863 +vt 0.540979 0.144196 +vt 0.502315 0.154853 +vt 0.501956 0.158323 +vt 0.501815 0.159685 +vt 0.501861 0.159235 +vt 0.502065 0.157269 +vt 0.502394 0.154083 +vt 0.502819 0.149972 +vt 0.503309 0.145232 +vt 0.503834 0.140160 +vt 0.504362 0.135050 +vt 0.504864 0.130200 +vt 0.466623 0.150350 +vt 0.466623 0.153865 +vt 0.466623 0.155245 +vt 0.466623 0.154789 +vt 0.466623 0.152797 +vt 0.466623 0.149569 +vt 0.466623 0.145405 +vt 0.466623 0.140603 +vt 0.466623 0.135465 +vt 0.466623 0.130288 +vt 0.466623 0.125375 +vt 0.427534 0.154853 +vt 0.428813 0.158323 +vt 0.429692 0.159685 +vt 0.430219 0.159235 +vt 0.430448 0.157269 +vt 0.430427 0.154083 +vt 0.430210 0.149972 +vt 0.429845 0.145232 +vt 0.429385 0.140160 +vt 0.428881 0.135050 +vt 0.428382 0.130200 +vt 0.391854 0.167916 +vt 0.394007 0.171255 +vt 0.395446 0.172565 +vt 0.396264 0.172132 +vt 0.396550 0.170241 +vt 0.396398 0.167175 +vt 0.395899 0.163220 +vt 0.395145 0.158659 +vt 0.394227 0.153779 +vt 0.393237 0.148863 +vt 0.392267 0.144196 +vt 0.359751 0.188867 +vt 0.362435 0.191995 +vt 0.364171 0.193223 +vt 0.365081 0.192817 +vt 0.365291 0.191045 +vt 0.364922 0.188173 +vt 0.364099 0.184467 +vt 0.362944 0.180194 +vt 0.361582 0.175622 +vt 0.360134 0.171016 +vt 0.358725 0.166643 +vt 0.331390 0.217035 +vt 0.334326 0.219880 +vt 0.336147 0.220996 +vt 0.336999 0.220628 +vt 0.337031 0.219016 +vt 0.336388 0.216403 +vt 0.335217 0.213033 +vt 0.333667 0.209147 +vt 0.331883 0.204989 +vt 0.330013 0.200800 +vt 0.328203 0.196823 +vt 0.306937 0.251748 +vt 0.309911 0.254244 +vt 0.311658 0.255224 +vt 0.312343 0.254900 +vt 0.312129 0.253486 +vt 0.311181 0.251194 +vt 0.309661 0.248237 +vt 0.307734 0.244828 +vt 0.305564 0.241180 +vt 0.303315 0.237505 +vt 0.301149 0.234016 +vt 0.286558 0.292336 +vt 0.289419 0.294424 +vt 0.290988 0.295243 +vt 0.291439 0.294972 +vt 0.290947 0.293789 +vt 0.289689 0.291872 +vt 0.287838 0.289399 +vt 0.285569 0.286547 +vt 0.283059 0.283495 +vt 0.280481 0.280421 +vt 0.278011 0.277503 +vt 0.270418 0.338126 +vt 0.273081 0.339753 +vt 0.274418 0.340392 +vt 0.274612 0.340181 +vt 0.273845 0.339259 +vt 0.272299 0.337765 +vt 0.270155 0.335837 +vt 0.267594 0.333614 +vt 0.264800 0.331235 +vt 0.261953 0.328838 +vt 0.259235 0.326564 +vt 0.258685 0.388448 +vt 0.261126 0.389569 +vt 0.262232 0.390009 +vt 0.262190 0.389864 +vt 0.261183 0.389229 +vt 0.259398 0.388199 +vt 0.257019 0.386870 +vt 0.254232 0.385338 +vt 0.251221 0.383699 +vt 0.248172 0.382047 +vt 0.245270 0.380480 +vt 0.251523 0.442630 +vt 0.253784 0.443206 +vt 0.254713 0.443433 +vt 0.254497 0.443358 +vt 0.253322 0.443031 +vt 0.251374 0.442501 +vt 0.248840 0.441818 +vt 0.245905 0.441030 +vt 0.242756 0.440187 +vt 0.239580 0.439338 +vt 0.236563 0.438532 +vt 0.249099 0.500000 +vt 0.251286 0.500000 +vt 0.252144 0.500000 +vt 0.251861 0.500000 +vt 0.250621 0.500000 +vt 0.248613 0.500000 +vt 0.246022 0.500000 +vt 0.243035 0.500000 +vt 0.239839 0.500000 +vt 0.236618 0.500000 +vt 0.233561 0.500000 +vt 0.251901 0.557371 +vt 0.254059 0.556794 +vt 0.254907 0.556568 +vt 0.254627 0.556642 +vt 0.253404 0.556969 +vt 0.251421 0.557499 +vt 0.248864 0.558182 +vt 0.245915 0.558970 +vt 0.242760 0.559813 +vt 0.239581 0.560662 +vt 0.236563 0.561469 +vt 0.260027 0.611553 +vt 0.262104 0.610431 +vt 0.262920 0.609991 +vt 0.262650 0.610136 +vt 0.261473 0.610772 +vt 0.259566 0.611802 +vt 0.257105 0.613130 +vt 0.254268 0.614662 +vt 0.251232 0.616301 +vt 0.248174 0.617953 +vt 0.245270 0.619521 +vt 0.273061 0.661874 +vt 0.275007 0.660247 +vt 0.275771 0.659608 +vt 0.275519 0.659819 +vt 0.274416 0.660741 +vt 0.272629 0.662235 +vt 0.270324 0.664164 +vt 0.267666 0.666386 +vt 0.264821 0.668765 +vt 0.261956 0.671162 +vt 0.259236 0.673437 +vt 0.290585 0.707664 +vt 0.292355 0.705577 +vt 0.293050 0.704757 +vt 0.292820 0.705028 +vt 0.291817 0.706211 +vt 0.290192 0.708128 +vt 0.288095 0.710601 +vt 0.285678 0.713453 +vt 0.283091 0.716505 +vt 0.280485 0.719579 +vt 0.278011 0.722498 +vt 0.312181 0.748252 +vt 0.313734 0.745756 +vt 0.314343 0.744776 +vt 0.314142 0.745100 +vt 0.313262 0.746514 +vt 0.311836 0.748806 +vt 0.309997 0.751763 +vt 0.307876 0.755172 +vt 0.305606 0.758820 +vt 0.303320 0.762495 +vt 0.301149 0.765984 +vt 0.337431 0.782965 +vt 0.338730 0.780120 +vt 0.339240 0.779004 +vt 0.339071 0.779372 +vt 0.338336 0.780984 +vt 0.337143 0.783597 +vt 0.335604 0.786967 +vt 0.333830 0.790853 +vt 0.331931 0.795011 +vt 0.330019 0.799200 +vt 0.328203 0.803177 +vt 0.365918 0.811133 +vt 0.366931 0.808005 +vt 0.367328 0.806777 +vt 0.367197 0.807183 +vt 0.366623 0.808955 +vt 0.365693 0.811827 +vt 0.364494 0.815533 +vt 0.363111 0.819806 +vt 0.361631 0.824378 +vt 0.360140 0.828984 +vt 0.358725 0.833357 +vt 0.397224 0.832084 +vt 0.397922 0.828745 +vt 0.398196 0.827435 +vt 0.398105 0.827868 +vt 0.397710 0.829759 +vt 0.397069 0.832825 +vt 0.396243 0.836781 +vt 0.395290 0.841341 +vt 0.394270 0.846221 +vt 0.393243 0.851137 +vt 0.392267 0.855804 +vt 0.430932 0.845147 +vt 0.431291 0.841677 +vt 0.431432 0.840315 +vt 0.431385 0.840765 +vt 0.431182 0.842731 +vt 0.430852 0.845917 +vt 0.430427 0.850028 +vt 0.429937 0.854768 +vt 0.429412 0.859840 +vt 0.428884 0.864950 +vt 0.428383 0.869800 +vt 0.466623 0.849650 +vt 0.466623 0.846135 +vt 0.466623 0.844755 +vt 0.466623 0.845211 +vt 0.466623 0.847203 +vt 0.466623 0.850431 +vt 0.466623 0.854595 +vt 0.466623 0.859397 +vt 0.466623 0.864535 +vt 0.466623 0.869712 +vt 0.466623 0.874625 +vt 0.502315 0.845147 +vt 0.501956 0.841677 +vt 0.501815 0.840315 +vt 0.501862 0.840765 +vt 0.502065 0.842731 +vt 0.502394 0.845917 +vt 0.502819 0.850028 +vt 0.503310 0.854768 +vt 0.503834 0.859840 +vt 0.504363 0.864950 +vt 0.504864 0.869800 +vt 0.536022 0.832084 +vt 0.535325 0.828745 +vt 0.535051 0.827435 +vt 0.535141 0.827868 +vt 0.535536 0.829759 +vt 0.536177 0.832825 +vt 0.537004 0.836780 +vt 0.537957 0.841341 +vt 0.538977 0.846221 +vt 0.540004 0.851137 +vt 0.540979 0.855804 +vt 0.567328 0.811133 +vt 0.566316 0.808005 +vt 0.565919 0.806777 +vt 0.566050 0.807183 +vt 0.566623 0.808955 +vt 0.567553 0.811827 +vt 0.568753 0.815533 +vt 0.570136 0.819806 +vt 0.571616 0.824378 +vt 0.573106 0.828984 +vt 0.574522 0.833357 +vt 0.595815 0.782965 +vt 0.594517 0.780120 +vt 0.594007 0.779004 +vt 0.594175 0.779372 +vt 0.594911 0.780984 +vt 0.596104 0.783597 +vt 0.597643 0.786967 +vt 0.599417 0.790853 +vt 0.601315 0.795011 +vt 0.603228 0.799200 +vt 0.605043 0.803177 +vt 0.621066 0.748252 +vt 0.619513 0.745756 +vt 0.618903 0.744776 +vt 0.619105 0.745100 +vt 0.619985 0.746514 +vt 0.621410 0.748806 +vt 0.623250 0.751763 +vt 0.625371 0.755172 +vt 0.627640 0.758820 +vt 0.629927 0.762495 +vt 0.632097 0.765984 +vt 0.642661 0.707664 +vt 0.640892 0.705576 +vt 0.640197 0.704757 +vt 0.640426 0.705028 +vt 0.641429 0.706211 +vt 0.643054 0.708128 +vt 0.645151 0.710601 +vt 0.647568 0.713453 +vt 0.650156 0.716505 +vt 0.652762 0.719579 +vt 0.655236 0.722497 +vt 0.660185 0.661874 +vt 0.658239 0.660247 +vt 0.657475 0.659608 +vt 0.657728 0.659819 +vt 0.658830 0.660741 +vt 0.660617 0.662235 +vt 0.662923 0.664163 +vt 0.665581 0.666386 +vt 0.668425 0.668765 +vt 0.671291 0.671162 +vt 0.674011 0.673436 +vt 0.673219 0.611552 +vt 0.671142 0.610431 +vt 0.670327 0.609991 +vt 0.670596 0.610136 +vt 0.671773 0.610771 +vt 0.673680 0.611801 +vt 0.676141 0.613130 +vt 0.678978 0.614662 +vt 0.682014 0.616301 +vt 0.685073 0.617953 +vt 0.687976 0.619520 +vt 0.681346 0.557370 +vt 0.679187 0.556794 +vt 0.678340 0.556567 +vt 0.678620 0.556642 +vt 0.679843 0.556969 +vt 0.681825 0.557499 +vt 0.684383 0.558182 +vt 0.687331 0.558970 +vt 0.690487 0.559813 +vt 0.693666 0.560662 +vt 0.696683 0.561468 +vt 0.708148 0.435468 +vt 0.711299 0.500000 +vt 0.719383 0.432467 +vt 0.722681 0.500000 +vt 0.730157 0.429588 +vt 0.733596 0.500000 +vt 0.740241 0.426893 +vt 0.743811 0.500000 +vt 0.749405 0.424445 +vt 0.753095 0.500000 +vt 0.757419 0.422304 +vt 0.761213 0.500000 +vt 0.764053 0.420531 +vt 0.767933 0.500000 +vt 0.769075 0.419189 +vt 0.773022 0.500000 +vt 0.772258 0.418339 +vt 0.776246 0.500000 +vt 0.773370 0.418042 +vt 0.777372 0.500000 +vt 0.699007 0.374523 +vt 0.709816 0.368687 +vt 0.720183 0.363089 +vt 0.729886 0.357850 +vt 0.738703 0.353089 +vt 0.746413 0.348926 +vt 0.752796 0.345480 +vt 0.757628 0.342870 +vt 0.760690 0.341217 +vt 0.761760 0.340639 +vt 0.684346 0.317920 +vt 0.694473 0.309451 +vt 0.704186 0.301328 +vt 0.713276 0.293726 +vt 0.721537 0.286817 +vt 0.728761 0.280776 +vt 0.734741 0.275775 +vt 0.739269 0.271989 +vt 0.742138 0.269589 +vt 0.743140 0.268751 +vt 0.664635 0.266415 +vt 0.673845 0.255549 +vt 0.682679 0.245129 +vt 0.690946 0.235376 +vt 0.698459 0.226513 +vt 0.705029 0.218763 +vt 0.710467 0.212348 +vt 0.714586 0.207490 +vt 0.717195 0.204412 +vt 0.718106 0.203337 +vt 0.640343 0.220761 +vt 0.648424 0.207772 +vt 0.656174 0.195315 +vt 0.663427 0.183656 +vt 0.670018 0.173061 +vt 0.675782 0.163796 +vt 0.680553 0.156127 +vt 0.684166 0.150320 +vt 0.686455 0.146640 +vt 0.687255 0.145355 +vt 0.611941 0.181715 +vt 0.618701 0.166910 +vt 0.625183 0.152711 +vt 0.631251 0.139422 +vt 0.636765 0.127345 +vt 0.641586 0.116784 +vt 0.645577 0.108043 +vt 0.648600 0.101423 +vt 0.650514 0.097230 +vt 0.651183 0.095764 +vt 0.579898 0.150031 +vt 0.585167 0.133752 +vt 0.590221 0.118140 +vt 0.594950 0.103528 +vt 0.599248 0.090249 +vt 0.603007 0.078637 +vt 0.606118 0.069025 +vt 0.608473 0.061747 +vt 0.609966 0.057136 +vt 0.610488 0.055524 +vt 0.544685 0.126465 +vt 0.548316 0.109090 +vt 0.551798 0.092426 +vt 0.555057 0.076830 +vt 0.558019 0.062657 +vt 0.560609 0.050264 +vt 0.562753 0.040004 +vt 0.564377 0.032236 +vt 0.565405 0.027314 +vt 0.565765 0.025594 +vt 0.506770 0.111771 +vt 0.508637 0.093713 +vt 0.510428 0.076394 +vt 0.512104 0.060184 +vt 0.513627 0.045454 +vt 0.514960 0.032573 +vt 0.516062 0.021910 +vt 0.516897 0.013836 +vt 0.517426 0.008720 +vt 0.517611 0.006933 +vt 0.466623 0.106706 +vt 0.466623 0.088412 +vt 0.466623 0.070867 +vt 0.466623 0.054446 +vt 0.466623 0.039523 +vt 0.466623 0.026473 +vt 0.466623 0.015672 +vt 0.466623 0.007493 +vt 0.466623 0.002310 +vt 0.466623 0.000499 +vt 0.426477 0.111771 +vt 0.424609 0.093713 +vt 0.422818 0.076394 +vt 0.421142 0.060184 +vt 0.419619 0.045454 +vt 0.418287 0.032573 +vt 0.417184 0.021910 +vt 0.416349 0.013836 +vt 0.415820 0.008721 +vt 0.415635 0.006933 +vt 0.388562 0.126465 +vt 0.384931 0.109090 +vt 0.381448 0.092426 +vt 0.378189 0.076830 +vt 0.375227 0.062657 +vt 0.372637 0.050264 +vt 0.370493 0.040004 +vt 0.368870 0.032236 +vt 0.367841 0.027314 +vt 0.367482 0.025595 +vt 0.353348 0.150031 +vt 0.348079 0.133752 +vt 0.343026 0.118140 +vt 0.338296 0.103528 +vt 0.333998 0.090249 +vt 0.330240 0.078637 +vt 0.327129 0.069026 +vt 0.324773 0.061747 +vt 0.323280 0.057136 +vt 0.322759 0.055524 +vt 0.321305 0.181715 +vt 0.314546 0.166910 +vt 0.308063 0.152711 +vt 0.301996 0.139422 +vt 0.296482 0.127345 +vt 0.291660 0.116785 +vt 0.287669 0.108043 +vt 0.284647 0.101424 +vt 0.282732 0.097230 +vt 0.282063 0.095764 +vt 0.292903 0.220761 +vt 0.284823 0.207772 +vt 0.277073 0.195315 +vt 0.269820 0.183656 +vt 0.263228 0.173061 +vt 0.257464 0.163796 +vt 0.252693 0.156127 +vt 0.249080 0.150320 +vt 0.246791 0.146640 +vt 0.245991 0.145355 +vt 0.268612 0.266415 +vt 0.259401 0.255550 +vt 0.250568 0.245129 +vt 0.242300 0.235376 +vt 0.234787 0.226514 +vt 0.228217 0.218763 +vt 0.222779 0.212348 +vt 0.218661 0.207490 +vt 0.216052 0.204412 +vt 0.215140 0.203337 +vt 0.248901 0.317921 +vt 0.238773 0.309451 +vt 0.229061 0.301329 +vt 0.219970 0.293726 +vt 0.211709 0.286818 +vt 0.204485 0.280776 +vt 0.198505 0.275776 +vt 0.193977 0.271989 +vt 0.191109 0.269590 +vt 0.190106 0.268751 +vt 0.234240 0.374524 +vt 0.223430 0.368687 +vt 0.213064 0.363089 +vt 0.203361 0.357850 +vt 0.194544 0.353090 +vt 0.186833 0.348926 +vt 0.180451 0.345480 +vt 0.175618 0.342871 +vt 0.172556 0.341217 +vt 0.171486 0.340640 +vt 0.225099 0.435468 +vt 0.213864 0.432467 +vt 0.203089 0.429588 +vt 0.193005 0.426894 +vt 0.183841 0.424445 +vt 0.175827 0.422304 +vt 0.169194 0.420532 +vt 0.164171 0.419190 +vt 0.160988 0.418339 +vt 0.159877 0.418042 +vt 0.221947 0.500000 +vt 0.210566 0.500000 +vt 0.199651 0.500000 +vt 0.189435 0.500000 +vt 0.180151 0.500000 +vt 0.172033 0.500000 +vt 0.165313 0.500000 +vt 0.160225 0.500000 +vt 0.157001 0.500000 +vt 0.155874 0.500000 +vt 0.225099 0.564532 +vt 0.213864 0.567533 +vt 0.203090 0.570412 +vt 0.193005 0.573107 +vt 0.183841 0.575555 +vt 0.175827 0.577696 +vt 0.169194 0.579469 +vt 0.164171 0.580811 +vt 0.160989 0.581661 +vt 0.159877 0.581958 +vt 0.234240 0.625477 +vt 0.223430 0.631313 +vt 0.213064 0.636911 +vt 0.203361 0.642150 +vt 0.194544 0.646911 +vt 0.186833 0.651074 +vt 0.180451 0.654520 +vt 0.175618 0.657130 +vt 0.172556 0.658783 +vt 0.171486 0.659361 +vt 0.248901 0.682080 +vt 0.238773 0.690549 +vt 0.229061 0.698672 +vt 0.219970 0.706274 +vt 0.211709 0.713183 +vt 0.204485 0.719224 +vt 0.198505 0.724225 +vt 0.193977 0.728011 +vt 0.191109 0.730411 +vt 0.190106 0.731249 +vt 0.268612 0.733585 +vt 0.259401 0.744451 +vt 0.250568 0.754871 +vt 0.242300 0.764624 +vt 0.234787 0.773487 +vt 0.228217 0.781237 +vt 0.222779 0.787652 +vt 0.218661 0.792510 +vt 0.216052 0.795588 +vt 0.215140 0.796663 +vt 0.292903 0.779239 +vt 0.284823 0.792228 +vt 0.277073 0.804685 +vt 0.269820 0.816344 +vt 0.263228 0.826939 +vt 0.257464 0.836204 +vt 0.252693 0.843873 +vt 0.249080 0.849680 +vt 0.246791 0.853360 +vt 0.245991 0.854645 +vt 0.321305 0.818285 +vt 0.314546 0.833090 +vt 0.308063 0.847289 +vt 0.301996 0.860578 +vt 0.296482 0.872655 +vt 0.291660 0.883216 +vt 0.287669 0.891957 +vt 0.284647 0.898577 +vt 0.282732 0.902771 +vt 0.282063 0.904236 +vt 0.353348 0.849969 +vt 0.348079 0.866248 +vt 0.343026 0.881860 +vt 0.338296 0.896472 +vt 0.333998 0.909751 +vt 0.330240 0.921363 +vt 0.327129 0.930975 +vt 0.324773 0.938253 +vt 0.323280 0.942864 +vt 0.322759 0.944476 +vt 0.388562 0.873535 +vt 0.384931 0.890910 +vt 0.381448 0.907574 +vt 0.378189 0.923170 +vt 0.375227 0.937343 +vt 0.372637 0.949737 +vt 0.370493 0.959996 +vt 0.368870 0.967764 +vt 0.367841 0.972686 +vt 0.367482 0.974406 +vt 0.426477 0.888229 +vt 0.424609 0.906287 +vt 0.422819 0.923606 +vt 0.421142 0.939816 +vt 0.419619 0.954546 +vt 0.418287 0.967427 +vt 0.417184 0.978090 +vt 0.416349 0.986164 +vt 0.415820 0.991280 +vt 0.415636 0.993067 +vt 0.466623 0.893294 +vt 0.466623 0.911588 +vt 0.466623 0.929133 +vt 0.466623 0.945554 +vt 0.466623 0.960477 +vt 0.466623 0.973526 +vt 0.466623 0.984328 +vt 0.466623 0.992507 +vt 0.466623 0.997690 +vt 0.466623 0.999501 +vt 0.506770 0.888229 +vt 0.508637 0.906287 +vt 0.510428 0.923606 +vt 0.512104 0.939816 +vt 0.513628 0.954546 +vt 0.514960 0.967427 +vt 0.516062 0.978090 +vt 0.516897 0.986164 +vt 0.517426 0.991279 +vt 0.517611 0.993067 +vt 0.544685 0.873535 +vt 0.548316 0.890910 +vt 0.551798 0.907574 +vt 0.555057 0.923170 +vt 0.558019 0.937343 +vt 0.560609 0.949736 +vt 0.562753 0.959996 +vt 0.564377 0.967764 +vt 0.565405 0.972686 +vt 0.565765 0.974406 +vt 0.579899 0.849969 +vt 0.585168 0.866248 +vt 0.590221 0.881860 +vt 0.594950 0.896472 +vt 0.599248 0.909751 +vt 0.603007 0.921363 +vt 0.606118 0.930975 +vt 0.608474 0.938253 +vt 0.609966 0.942864 +vt 0.610488 0.944475 +vt 0.611941 0.818285 +vt 0.618701 0.833090 +vt 0.625184 0.847289 +vt 0.631251 0.860578 +vt 0.636765 0.872655 +vt 0.641586 0.883215 +vt 0.645577 0.891957 +vt 0.648600 0.898576 +vt 0.650514 0.902770 +vt 0.651183 0.904236 +vt 0.640343 0.779239 +vt 0.648424 0.792228 +vt 0.656174 0.804685 +vt 0.663427 0.816344 +vt 0.670018 0.826939 +vt 0.675782 0.836204 +vt 0.680553 0.843873 +vt 0.684166 0.849680 +vt 0.686455 0.853360 +vt 0.687255 0.854645 +vt 0.664635 0.733585 +vt 0.673845 0.744450 +vt 0.682679 0.754871 +vt 0.690946 0.764624 +vt 0.698459 0.773486 +vt 0.705029 0.781237 +vt 0.710468 0.787652 +vt 0.714586 0.792510 +vt 0.717195 0.795588 +vt 0.718106 0.796663 +vt 0.684346 0.682079 +vt 0.694473 0.690549 +vt 0.704186 0.698671 +vt 0.713277 0.706274 +vt 0.721537 0.713182 +vt 0.728761 0.719224 +vt 0.734741 0.724224 +vt 0.739269 0.728011 +vt 0.742138 0.730410 +vt 0.743140 0.731249 +vt 0.699007 0.625476 +vt 0.709816 0.631313 +vt 0.720183 0.636911 +vt 0.729886 0.642150 +vt 0.738703 0.646910 +vt 0.746413 0.651074 +vt 0.752796 0.654520 +vt 0.757629 0.657129 +vt 0.760691 0.658783 +vt 0.761760 0.659360 +vt 0.708148 0.564532 +vt 0.719383 0.567533 +vt 0.730157 0.570412 +vt 0.740241 0.573106 +vt 0.749405 0.575555 +vt 0.757419 0.577696 +vt 0.764053 0.579468 +vt 0.769076 0.580810 +vt 0.772258 0.581661 +vt 0.773370 0.581958 +vt 0.771223 0.418616 +vt 0.775197 0.500000 +vt 0.765395 0.420173 +vt 0.769293 0.500000 +vt 0.756806 0.422468 +vt 0.760592 0.500000 +vt 0.746376 0.425254 +vt 0.750026 0.500000 +vt 0.735027 0.428287 +vt 0.738529 0.500000 +vt 0.723677 0.431319 +vt 0.727031 0.500000 +vt 0.713248 0.434106 +vt 0.716466 0.500000 +vt 0.704659 0.436400 +vt 0.707765 0.500000 +vt 0.698830 0.437958 +vt 0.701860 0.500000 +vt 0.759694 0.341755 +vt 0.754087 0.344783 +vt 0.745823 0.349245 +vt 0.735788 0.354663 +vt 0.724868 0.360559 +vt 0.713948 0.366456 +vt 0.703913 0.371874 +vt 0.695650 0.376336 +vt 0.690042 0.379364 +vt 0.741205 0.270370 +vt 0.735951 0.274764 +vt 0.728208 0.281239 +vt 0.718807 0.289101 +vt 0.708576 0.297657 +vt 0.698344 0.306213 +vt 0.688943 0.314076 +vt 0.681200 0.320551 +vt 0.675947 0.324945 +vt 0.716346 0.205413 +vt 0.711568 0.211050 +vt 0.704526 0.219356 +vt 0.695976 0.229443 +vt 0.686671 0.240420 +vt 0.677366 0.251396 +vt 0.668816 0.261483 +vt 0.661774 0.269789 +vt 0.656996 0.275426 +vt 0.685711 0.147837 +vt 0.681519 0.154575 +vt 0.675341 0.164505 +vt 0.667839 0.176563 +vt 0.659676 0.189685 +vt 0.651513 0.202807 +vt 0.644011 0.214865 +vt 0.637834 0.224795 +vt 0.633642 0.231533 +vt 0.649891 0.098594 +vt 0.646385 0.106274 +vt 0.641217 0.117593 +vt 0.634942 0.131337 +vt 0.628113 0.146294 +vt 0.621285 0.161250 +vt 0.615010 0.174994 +vt 0.609842 0.186313 +vt 0.606335 0.193994 +vt 0.609481 0.058636 +vt 0.606747 0.067081 +vt 0.602719 0.079526 +vt 0.597828 0.094638 +vt 0.592505 0.111084 +vt 0.587182 0.127529 +vt 0.582290 0.142642 +vt 0.578262 0.155087 +vt 0.575529 0.163532 +vt 0.565071 0.028915 +vt 0.563187 0.037929 +vt 0.560411 0.051212 +vt 0.557040 0.067342 +vt 0.553372 0.084895 +vt 0.549704 0.102448 +vt 0.546333 0.118578 +vt 0.543557 0.131861 +vt 0.541673 0.140875 +vt 0.517254 0.010385 +vt 0.516285 0.019753 +vt 0.514858 0.033559 +vt 0.513124 0.050323 +vt 0.511237 0.068566 +vt 0.509351 0.086810 +vt 0.507617 0.103574 +vt 0.506190 0.117380 +vt 0.505221 0.126748 +vt 0.466623 0.003996 +vt 0.466623 0.013486 +vt 0.466623 0.027473 +vt 0.466623 0.044455 +vt 0.466623 0.062937 +vt 0.466623 0.081419 +vt 0.466623 0.098402 +vt 0.466623 0.112388 +vt 0.466623 0.121878 +vt 0.415992 0.010385 +vt 0.416961 0.019753 +vt 0.418389 0.033559 +vt 0.420122 0.050323 +vt 0.422009 0.068567 +vt 0.423895 0.086810 +vt 0.425629 0.103574 +vt 0.427057 0.117380 +vt 0.428025 0.126748 +vt 0.368176 0.028915 +vt 0.370059 0.037929 +vt 0.372835 0.051212 +vt 0.376206 0.067342 +vt 0.379874 0.084895 +vt 0.383543 0.102448 +vt 0.386914 0.118578 +vt 0.389689 0.131861 +vt 0.391573 0.140875 +vt 0.323766 0.058636 +vt 0.326499 0.067081 +vt 0.330527 0.079526 +vt 0.335419 0.094638 +vt 0.340742 0.111084 +vt 0.346065 0.127530 +vt 0.350956 0.142642 +vt 0.354984 0.155087 +vt 0.357718 0.163532 +vt 0.283355 0.098594 +vt 0.286862 0.106274 +vt 0.292029 0.117593 +vt 0.298304 0.131337 +vt 0.305133 0.146294 +vt 0.311962 0.161250 +vt 0.318237 0.174995 +vt 0.323405 0.186313 +vt 0.326911 0.193994 +vt 0.247536 0.147837 +vt 0.251728 0.154576 +vt 0.257905 0.164506 +vt 0.265407 0.176564 +vt 0.273570 0.189685 +vt 0.281734 0.202807 +vt 0.289235 0.214865 +vt 0.295413 0.224795 +vt 0.299605 0.231534 +vt 0.216900 0.205413 +vt 0.221679 0.211050 +vt 0.228720 0.219357 +vt 0.237271 0.229443 +vt 0.246576 0.240420 +vt 0.255880 0.251396 +vt 0.264431 0.261483 +vt 0.271472 0.269789 +vt 0.276251 0.275426 +vt 0.192042 0.270370 +vt 0.197296 0.274764 +vt 0.205038 0.281239 +vt 0.214440 0.289101 +vt 0.224671 0.297657 +vt 0.234902 0.306214 +vt 0.244304 0.314076 +vt 0.252046 0.320551 +vt 0.257300 0.324945 +vt 0.173552 0.341755 +vt 0.179160 0.344783 +vt 0.187423 0.349245 +vt 0.197458 0.354663 +vt 0.208378 0.360560 +vt 0.219298 0.366456 +vt 0.229333 0.371874 +vt 0.237597 0.376336 +vt 0.243204 0.379364 +vt 0.162024 0.418616 +vt 0.167852 0.420173 +vt 0.176441 0.422468 +vt 0.186870 0.425254 +vt 0.198220 0.428287 +vt 0.209570 0.431319 +vt 0.219999 0.434106 +vt 0.228588 0.436401 +vt 0.234416 0.437958 +vt 0.158049 0.500000 +vt 0.163954 0.500000 +vt 0.172655 0.500000 +vt 0.183220 0.500000 +vt 0.194718 0.500000 +vt 0.206215 0.500000 +vt 0.216781 0.500000 +vt 0.225482 0.500000 +vt 0.231386 0.500000 +vt 0.162024 0.581384 +vt 0.167852 0.579827 +vt 0.176441 0.577532 +vt 0.186870 0.574746 +vt 0.198220 0.571713 +vt 0.209570 0.568681 +vt 0.219999 0.565894 +vt 0.228588 0.563600 +vt 0.234416 0.562042 +vt 0.173552 0.658245 +vt 0.179160 0.655217 +vt 0.187424 0.650755 +vt 0.197458 0.645337 +vt 0.208378 0.639441 +vt 0.219298 0.633544 +vt 0.229333 0.628126 +vt 0.237597 0.623664 +vt 0.243204 0.620636 +vt 0.192042 0.729630 +vt 0.197296 0.725236 +vt 0.205038 0.718761 +vt 0.214440 0.710899 +vt 0.224671 0.702343 +vt 0.234902 0.693787 +vt 0.244304 0.685924 +vt 0.252046 0.679449 +vt 0.257300 0.675055 +vt 0.216901 0.794587 +vt 0.221679 0.788950 +vt 0.228720 0.780644 +vt 0.237271 0.770557 +vt 0.246576 0.759580 +vt 0.255880 0.748604 +vt 0.264431 0.738517 +vt 0.271472 0.730211 +vt 0.276251 0.724574 +vt 0.247536 0.852163 +vt 0.251728 0.845425 +vt 0.257906 0.835495 +vt 0.265407 0.823437 +vt 0.273570 0.810315 +vt 0.281734 0.797193 +vt 0.289235 0.785135 +vt 0.295413 0.775205 +vt 0.299605 0.768467 +vt 0.283355 0.901406 +vt 0.286862 0.893726 +vt 0.292029 0.882407 +vt 0.298304 0.868663 +vt 0.305133 0.853706 +vt 0.311962 0.838750 +vt 0.318237 0.825006 +vt 0.323405 0.813687 +vt 0.326911 0.806006 +vt 0.323766 0.941364 +vt 0.326499 0.932919 +vt 0.330528 0.920474 +vt 0.335419 0.905362 +vt 0.340742 0.888916 +vt 0.346065 0.872471 +vt 0.350956 0.857358 +vt 0.354985 0.844913 +vt 0.357718 0.836468 +vt 0.368176 0.971085 +vt 0.370060 0.962071 +vt 0.372836 0.948788 +vt 0.376206 0.932658 +vt 0.379875 0.915105 +vt 0.383543 0.897552 +vt 0.386914 0.881422 +vt 0.389690 0.868139 +vt 0.391573 0.859125 +vt 0.415993 0.989615 +vt 0.416961 0.980247 +vt 0.418389 0.966441 +vt 0.420123 0.949677 +vt 0.422009 0.931434 +vt 0.423896 0.913190 +vt 0.425629 0.896426 +vt 0.427057 0.882620 +vt 0.428026 0.873252 +vt 0.466623 0.996004 +vt 0.466623 0.986513 +vt 0.466623 0.972527 +vt 0.466623 0.955544 +vt 0.466623 0.937063 +vt 0.466623 0.918581 +vt 0.466623 0.901598 +vt 0.466623 0.887612 +vt 0.466623 0.878122 +vt 0.517254 0.989615 +vt 0.516285 0.980247 +vt 0.514858 0.966441 +vt 0.513124 0.949677 +vt 0.511238 0.931433 +vt 0.509351 0.913190 +vt 0.507617 0.896426 +vt 0.506190 0.882620 +vt 0.505221 0.873252 +vt 0.565071 0.971085 +vt 0.563187 0.962071 +vt 0.560411 0.948788 +vt 0.557040 0.932658 +vt 0.553372 0.915105 +vt 0.549704 0.897552 +vt 0.546333 0.881422 +vt 0.543557 0.868139 +vt 0.541673 0.859125 +vt 0.609481 0.941364 +vt 0.606747 0.932919 +vt 0.602719 0.920474 +vt 0.597828 0.905362 +vt 0.592505 0.888916 +vt 0.587182 0.872470 +vt 0.582290 0.857358 +vt 0.578262 0.844913 +vt 0.575529 0.836468 +vt 0.649891 0.901406 +vt 0.646385 0.893726 +vt 0.641217 0.882407 +vt 0.634942 0.868663 +vt 0.628113 0.853706 +vt 0.621285 0.838750 +vt 0.615010 0.825005 +vt 0.609842 0.813687 +vt 0.606335 0.806006 +vt 0.685711 0.852163 +vt 0.681519 0.845424 +vt 0.675341 0.835494 +vt 0.667840 0.823436 +vt 0.659676 0.810315 +vt 0.651513 0.797193 +vt 0.644011 0.785135 +vt 0.637834 0.775205 +vt 0.633642 0.768466 +vt 0.716346 0.794587 +vt 0.711568 0.788950 +vt 0.704526 0.780643 +vt 0.695976 0.770557 +vt 0.686671 0.759580 +vt 0.677366 0.748604 +vt 0.668816 0.738517 +vt 0.661774 0.730211 +vt 0.656996 0.724574 +vt 0.741205 0.729630 +vt 0.735951 0.725236 +vt 0.728208 0.718761 +vt 0.718807 0.710899 +vt 0.708576 0.702343 +vt 0.698345 0.693786 +vt 0.688943 0.685924 +vt 0.681201 0.679449 +vt 0.675947 0.675055 +vt 0.759694 0.658245 +vt 0.754087 0.655217 +vt 0.745823 0.650755 +vt 0.735788 0.645337 +vt 0.724868 0.639440 +vt 0.713948 0.633544 +vt 0.703914 0.628126 +vt 0.695650 0.623664 +vt 0.690042 0.620636 +vt 0.771223 0.581384 +vt 0.765395 0.579827 +vt 0.756806 0.577532 +vt 0.746376 0.574746 +vt 0.735027 0.571713 +vt 0.723677 0.568681 +vt 0.713248 0.565894 +vt 0.704659 0.563599 +vt 0.698830 0.562042 +vt 0.696143 0.438676 +vt 0.699137 0.500000 +vt 0.693738 0.439318 +vt 0.696702 0.500000 +vt 0.688298 0.440772 +vt 0.691190 0.500000 +vt 0.678647 0.443351 +vt 0.681413 0.500000 +vt 0.663612 0.447367 +vt 0.666182 0.500000 +vt 0.642021 0.453136 +vt 0.644310 0.500000 +vt 0.612700 0.460971 +vt 0.614606 0.500000 +vt 0.574475 0.471184 +vt 0.575883 0.500000 +vt 0.526174 0.484089 +vt 0.526951 0.500000 +vt 0.466623 0.500000 +vt 0.687456 0.380760 +vt 0.685143 0.382009 +vt 0.679908 0.384836 +vt 0.670622 0.389850 +vt 0.656157 0.397660 +vt 0.635383 0.408877 +vt 0.607171 0.424110 +vt 0.570393 0.443969 +vt 0.523920 0.469062 +vt 0.673524 0.326971 +vt 0.671356 0.328783 +vt 0.666452 0.332885 +vt 0.657752 0.340161 +vt 0.644199 0.351495 +vt 0.624736 0.367772 +vt 0.598304 0.389876 +vt 0.563847 0.418693 +vt 0.520306 0.455106 +vt 0.654792 0.278025 +vt 0.652821 0.280350 +vt 0.648361 0.285612 +vt 0.640448 0.294946 +vt 0.628123 0.309486 +vt 0.610421 0.330368 +vt 0.586383 0.358725 +vt 0.555045 0.395693 +vt 0.515445 0.442407 +vt 0.631708 0.234641 +vt 0.629979 0.237421 +vt 0.626066 0.243711 +vt 0.619124 0.254869 +vt 0.608310 0.272251 +vt 0.592781 0.297214 +vt 0.571691 0.331113 +vt 0.544197 0.375307 +vt 0.509456 0.431150 +vt 0.604718 0.197536 +vt 0.603271 0.200704 +vt 0.599998 0.207874 +vt 0.594191 0.220592 +vt 0.585145 0.240405 +vt 0.572155 0.268858 +vt 0.554513 0.307498 +vt 0.531515 0.357871 +vt 0.502453 0.421523 +vt 0.574268 0.167427 +vt 0.573140 0.170910 +vt 0.570589 0.178794 +vt 0.566062 0.192778 +vt 0.559011 0.214563 +vt 0.548885 0.245849 +vt 0.535133 0.288335 +vt 0.517206 0.343722 +vt 0.494553 0.413711 +vt 0.540805 0.145032 +vt 0.540027 0.148750 +vt 0.538269 0.157165 +vt 0.535150 0.172091 +vt 0.530291 0.195343 +vt 0.523312 0.228735 +vt 0.513836 0.274082 +vt 0.501481 0.333199 +vt 0.485870 0.407900 +vt 0.504774 0.131069 +vt 0.504374 0.134933 +vt 0.503470 0.143679 +vt 0.501866 0.159192 +vt 0.499367 0.183359 +vt 0.495778 0.218064 +vt 0.490904 0.265195 +vt 0.484550 0.326638 +vt 0.476522 0.404277 +vt 0.466623 0.126255 +vt 0.466623 0.130170 +vt 0.466623 0.139030 +vt 0.466623 0.154745 +vt 0.466623 0.179227 +vt 0.466623 0.214386 +vt 0.466623 0.262132 +vt 0.466623 0.324376 +vt 0.466623 0.403028 +vt 0.428472 0.131069 +vt 0.428872 0.134933 +vt 0.429776 0.143679 +vt 0.431380 0.159192 +vt 0.433880 0.183359 +vt 0.437468 0.218064 +vt 0.442342 0.265195 +vt 0.448696 0.326638 +vt 0.456725 0.404277 +vt 0.392442 0.145032 +vt 0.393219 0.148750 +vt 0.394977 0.157165 +vt 0.398097 0.172091 +vt 0.402956 0.195343 +vt 0.409934 0.228735 +vt 0.419411 0.274082 +vt 0.431765 0.333199 +vt 0.447376 0.407900 +vt 0.358978 0.167427 +vt 0.360106 0.170910 +vt 0.362658 0.178794 +vt 0.367184 0.192779 +vt 0.374235 0.214563 +vt 0.384362 0.245849 +vt 0.398113 0.288335 +vt 0.416041 0.343722 +vt 0.438694 0.413711 +vt 0.328528 0.197536 +vt 0.329975 0.200704 +vt 0.333248 0.207874 +vt 0.339055 0.220592 +vt 0.348101 0.240405 +vt 0.361092 0.268858 +vt 0.378733 0.307498 +vt 0.401732 0.357871 +vt 0.430793 0.421523 +vt 0.301538 0.234641 +vt 0.303267 0.237421 +vt 0.307181 0.243711 +vt 0.314122 0.254869 +vt 0.324936 0.272251 +vt 0.340466 0.297214 +vt 0.361556 0.331114 +vt 0.389049 0.375307 +vt 0.423790 0.431150 +vt 0.278454 0.278025 +vt 0.280425 0.280351 +vt 0.284886 0.285613 +vt 0.292798 0.294946 +vt 0.305124 0.309487 +vt 0.322825 0.330368 +vt 0.346864 0.358725 +vt 0.378202 0.395693 +vt 0.417801 0.442407 +vt 0.259723 0.326971 +vt 0.261890 0.328784 +vt 0.266795 0.332885 +vt 0.275495 0.340161 +vt 0.289047 0.351495 +vt 0.308511 0.367772 +vt 0.334942 0.389877 +vt 0.369400 0.418693 +vt 0.412941 0.455106 +vt 0.245791 0.380761 +vt 0.248104 0.382010 +vt 0.253339 0.384836 +vt 0.262624 0.389850 +vt 0.277090 0.397661 +vt 0.297864 0.408878 +vt 0.326075 0.424111 +vt 0.362853 0.443969 +vt 0.409326 0.469062 +vt 0.237104 0.438676 +vt 0.239508 0.439318 +vt 0.244949 0.440772 +vt 0.254600 0.443351 +vt 0.269634 0.447368 +vt 0.291225 0.453137 +vt 0.320547 0.460971 +vt 0.358771 0.471184 +vt 0.407072 0.484089 +vt 0.234109 0.500000 +vt 0.236545 0.500000 +vt 0.242056 0.500000 +vt 0.251833 0.500000 +vt 0.267064 0.500000 +vt 0.288937 0.500000 +vt 0.318641 0.500000 +vt 0.357364 0.500000 +vt 0.406295 0.500000 +vt 0.237104 0.561324 +vt 0.239508 0.560682 +vt 0.244949 0.559228 +vt 0.254600 0.556649 +vt 0.269634 0.552633 +vt 0.291225 0.546864 +vt 0.320547 0.539029 +vt 0.358771 0.528816 +vt 0.407072 0.515911 +vt 0.245791 0.619240 +vt 0.248104 0.617991 +vt 0.253339 0.615164 +vt 0.262625 0.610150 +vt 0.277090 0.602340 +vt 0.297864 0.591122 +vt 0.326075 0.575890 +vt 0.362853 0.556031 +vt 0.409326 0.530938 +vt 0.259723 0.673029 +vt 0.261890 0.671217 +vt 0.266795 0.667115 +vt 0.275495 0.659839 +vt 0.289048 0.648505 +vt 0.308511 0.632228 +vt 0.334942 0.610124 +vt 0.369400 0.581307 +vt 0.412941 0.544894 +vt 0.278454 0.721975 +vt 0.280425 0.719650 +vt 0.284886 0.714388 +vt 0.292798 0.705054 +vt 0.305124 0.690514 +vt 0.322825 0.669632 +vt 0.346864 0.641275 +vt 0.378202 0.604307 +vt 0.417801 0.557593 +vt 0.301538 0.765359 +vt 0.303267 0.762579 +vt 0.307181 0.756289 +vt 0.314123 0.745131 +vt 0.324936 0.727749 +vt 0.340466 0.702786 +vt 0.361556 0.668887 +vt 0.389049 0.624693 +vt 0.423790 0.568850 +vt 0.328528 0.802464 +vt 0.329975 0.799296 +vt 0.333249 0.792126 +vt 0.339055 0.779408 +vt 0.348101 0.759595 +vt 0.361092 0.731142 +vt 0.378733 0.692502 +vt 0.401732 0.642129 +vt 0.430793 0.578477 +vt 0.358979 0.832573 +vt 0.360106 0.829090 +vt 0.362658 0.821206 +vt 0.367184 0.807222 +vt 0.374235 0.785437 +vt 0.384362 0.754151 +vt 0.398113 0.711665 +vt 0.416041 0.656278 +vt 0.438694 0.586289 +vt 0.392442 0.854968 +vt 0.393219 0.851250 +vt 0.394978 0.842835 +vt 0.398097 0.827909 +vt 0.402956 0.804657 +vt 0.409934 0.771265 +vt 0.419411 0.725918 +vt 0.431765 0.666801 +vt 0.447376 0.592100 +vt 0.428472 0.868931 +vt 0.428872 0.865067 +vt 0.429776 0.856321 +vt 0.431381 0.840808 +vt 0.433880 0.816641 +vt 0.437469 0.781936 +vt 0.442342 0.734805 +vt 0.448696 0.673362 +vt 0.456725 0.595723 +vt 0.466623 0.873745 +vt 0.466623 0.869830 +vt 0.466623 0.860970 +vt 0.466623 0.845255 +vt 0.466623 0.820773 +vt 0.466623 0.785614 +vt 0.466623 0.737868 +vt 0.466623 0.675624 +vt 0.466623 0.596972 +vt 0.504774 0.868931 +vt 0.504375 0.865067 +vt 0.503470 0.856321 +vt 0.501866 0.840808 +vt 0.499367 0.816641 +vt 0.495778 0.781936 +vt 0.490904 0.734805 +vt 0.484551 0.673362 +vt 0.476522 0.595723 +vt 0.540805 0.854968 +vt 0.540028 0.851250 +vt 0.538269 0.842835 +vt 0.535150 0.827909 +vt 0.530291 0.804657 +vt 0.523312 0.771265 +vt 0.513836 0.725918 +vt 0.501481 0.666801 +vt 0.485870 0.592100 +vt 0.574268 0.832573 +vt 0.573141 0.829090 +vt 0.570589 0.821206 +vt 0.566062 0.807221 +vt 0.559011 0.785437 +vt 0.548885 0.754151 +vt 0.535133 0.711665 +vt 0.517206 0.656278 +vt 0.494553 0.586289 +vt 0.604718 0.802464 +vt 0.603272 0.799296 +vt 0.599998 0.792126 +vt 0.594191 0.779408 +vt 0.585145 0.759595 +vt 0.572155 0.731142 +vt 0.554513 0.692502 +vt 0.531515 0.642129 +vt 0.502453 0.578477 +vt 0.631708 0.765359 +vt 0.629979 0.762579 +vt 0.626066 0.756289 +vt 0.619124 0.745131 +vt 0.608310 0.727749 +vt 0.592781 0.702786 +vt 0.571691 0.668886 +vt 0.544197 0.624693 +vt 0.509456 0.568850 +vt 0.654792 0.721975 +vt 0.652821 0.719649 +vt 0.648361 0.714387 +vt 0.640448 0.705054 +vt 0.628123 0.690513 +vt 0.610421 0.669632 +vt 0.586383 0.641275 +vt 0.555045 0.604307 +vt 0.515446 0.557593 +vt 0.673524 0.673029 +vt 0.671356 0.671216 +vt 0.666452 0.667115 +vt 0.657752 0.659839 +vt 0.644199 0.648505 +vt 0.624736 0.632228 +vt 0.598304 0.610123 +vt 0.563847 0.581307 +vt 0.520306 0.544894 +vt 0.687456 0.619239 +vt 0.685143 0.617990 +vt 0.679908 0.615164 +vt 0.670622 0.610150 +vt 0.656157 0.602339 +vt 0.635383 0.591122 +vt 0.607171 0.575889 +vt 0.570393 0.556031 +vt 0.523920 0.530938 +vt 0.696143 0.561324 +vt 0.693739 0.560682 +vt 0.688298 0.559228 +vt 0.678647 0.556649 +vt 0.663612 0.552632 +vt 0.642021 0.546863 +vt 0.612700 0.539029 +vt 0.574475 0.528816 +vt 0.526174 0.515911 +vt 0.218024 0.500000 +vt 0.218459 0.479770 +vt 0.186878 0.479770 +vt 0.186809 0.500000 +vt 0.158237 0.479770 +vt 0.158484 0.500000 +vt 0.132626 0.479770 +vt 0.133143 0.500000 +vt 0.110136 0.479770 +vt 0.110878 0.500000 +vt 0.090858 0.479770 +vt 0.091782 0.500000 +vt 0.074881 0.479770 +vt 0.075949 0.500000 +vt 0.062297 0.479770 +vt 0.063473 0.500000 +vt 0.053196 0.479770 +vt 0.054446 0.500000 +vt 0.047669 0.479770 +vt 0.048961 0.500000 +vt 0.045807 0.479770 +vt 0.047112 0.500000 +vt 0.219640 0.464036 +vt 0.187066 0.464036 +vt 0.157567 0.464036 +vt 0.131225 0.464036 +vt 0.108124 0.464036 +vt 0.088348 0.464036 +vt 0.071981 0.464036 +vt 0.059105 0.464036 +vt 0.049805 0.464036 +vt 0.044163 0.464036 +vt 0.042264 0.464036 +vt 0.221380 0.452797 +vt 0.187343 0.452797 +vt 0.156578 0.452797 +vt 0.129159 0.452797 +vt 0.105159 0.452797 +vt 0.084650 0.452797 +vt 0.067707 0.452797 +vt 0.054401 0.452797 +vt 0.044807 0.452797 +vt 0.038997 0.452797 +vt 0.037044 0.452797 +vt 0.223493 0.446054 +vt 0.187679 0.446054 +vt 0.155378 0.446054 +vt 0.126651 0.446054 +vt 0.101558 0.446054 +vt 0.080160 0.446054 +vt 0.062517 0.446054 +vt 0.048690 0.446054 +vt 0.038738 0.446054 +vt 0.032723 0.446054 +vt 0.030704 0.446054 +vt 0.225793 0.443806 +vt 0.188044 0.443806 +vt 0.154072 0.443806 +vt 0.123921 0.443806 +vt 0.097640 0.443806 +vt 0.075273 0.443806 +vt 0.056869 0.443806 +vt 0.042474 0.443806 +vt 0.032134 0.443806 +vt 0.025896 0.443806 +vt 0.023806 0.443806 +vt 0.228092 0.446054 +vt 0.188410 0.446054 +vt 0.152766 0.446054 +vt 0.121192 0.446054 +vt 0.093721 0.446054 +vt 0.070387 0.446054 +vt 0.051222 0.446054 +vt 0.036258 0.446054 +vt 0.025530 0.446054 +vt 0.019068 0.446054 +vt 0.016907 0.446054 +vt 0.230205 0.452797 +vt 0.188746 0.452797 +vt 0.151565 0.452797 +vt 0.118684 0.452797 +vt 0.090121 0.452797 +vt 0.065897 0.452797 +vt 0.046032 0.452797 +vt 0.030547 0.452797 +vt 0.019461 0.452797 +vt 0.012794 0.452797 +vt 0.010568 0.452797 +vt 0.231945 0.464036 +vt 0.189023 0.464036 +vt 0.150577 0.464036 +vt 0.116618 0.464036 +vt 0.087155 0.464036 +vt 0.062199 0.464036 +vt 0.041758 0.464036 +vt 0.025843 0.464036 +vt 0.014463 0.464036 +vt 0.007628 0.464036 +vt 0.005347 0.464036 +vt 0.233126 0.479770 +vt 0.189210 0.479770 +vt 0.149906 0.479770 +vt 0.115216 0.479770 +vt 0.085143 0.479770 +vt 0.059689 0.479770 +vt 0.038858 0.479770 +vt 0.022651 0.479770 +vt 0.011071 0.479770 +vt 0.004122 0.479770 +vt 0.001805 0.479770 +vt 0.189280 0.500000 +vt 0.149659 0.500000 +vt 0.114700 0.500000 +vt 0.084402 0.500000 +vt 0.058765 0.500000 +vt 0.037789 0.500000 +vt 0.021475 0.500000 +vt 0.009822 0.500000 +vt 0.002830 0.500000 +vt 0.000500 0.500000 +vt 0.233126 0.520230 +vt 0.189210 0.520230 +vt 0.149906 0.520230 +vt 0.115216 0.520230 +vt 0.085143 0.520230 +vt 0.059689 0.520230 +vt 0.038858 0.520230 +vt 0.022651 0.520230 +vt 0.011072 0.520230 +vt 0.004122 0.520230 +vt 0.001805 0.520230 +vt 0.231945 0.535964 +vt 0.189023 0.535964 +vt 0.150577 0.535964 +vt 0.116618 0.535964 +vt 0.087155 0.535964 +vt 0.062199 0.535964 +vt 0.041758 0.535964 +vt 0.025843 0.535964 +vt 0.014463 0.535964 +vt 0.007628 0.535964 +vt 0.005347 0.535964 +vt 0.230205 0.547203 +vt 0.188746 0.547203 +vt 0.151565 0.547203 +vt 0.118684 0.547203 +vt 0.090121 0.547203 +vt 0.065897 0.547203 +vt 0.046032 0.547203 +vt 0.030547 0.547203 +vt 0.019461 0.547203 +vt 0.012794 0.547203 +vt 0.010568 0.547203 +vt 0.228092 0.553946 +vt 0.188410 0.553946 +vt 0.152766 0.553946 +vt 0.121192 0.553946 +vt 0.093721 0.553946 +vt 0.070387 0.553946 +vt 0.051222 0.553946 +vt 0.036258 0.553946 +vt 0.025530 0.553946 +vt 0.019068 0.553946 +vt 0.016907 0.553946 +vt 0.225793 0.556194 +vt 0.188044 0.556194 +vt 0.154072 0.556194 +vt 0.123921 0.556194 +vt 0.097640 0.556194 +vt 0.075273 0.556194 +vt 0.056869 0.556194 +vt 0.042474 0.556194 +vt 0.032134 0.556194 +vt 0.025896 0.556194 +vt 0.023806 0.556194 +vt 0.223493 0.553946 +vt 0.187679 0.553946 +vt 0.155378 0.553946 +vt 0.126651 0.553946 +vt 0.101558 0.553946 +vt 0.080160 0.553946 +vt 0.062517 0.553946 +vt 0.048690 0.553946 +vt 0.038738 0.553946 +vt 0.032723 0.553946 +vt 0.030704 0.553946 +vt 0.221380 0.547203 +vt 0.187343 0.547203 +vt 0.156578 0.547203 +vt 0.129159 0.547203 +vt 0.105159 0.547203 +vt 0.084650 0.547203 +vt 0.067707 0.547203 +vt 0.054401 0.547203 +vt 0.044807 0.547203 +vt 0.038997 0.547203 +vt 0.037044 0.547203 +vt 0.219640 0.535964 +vt 0.187066 0.535964 +vt 0.157567 0.535964 +vt 0.131225 0.535964 +vt 0.108124 0.535964 +vt 0.088348 0.535964 +vt 0.071981 0.535964 +vt 0.059105 0.535964 +vt 0.049805 0.535964 +vt 0.044163 0.535964 +vt 0.042264 0.535964 +vt 0.218459 0.520230 +vt 0.186878 0.520230 +vt 0.158237 0.520230 +vt 0.132626 0.520230 +vt 0.110136 0.520230 +vt 0.090858 0.520230 +vt 0.074881 0.520230 +vt 0.062297 0.520230 +vt 0.053196 0.520230 +vt 0.047669 0.520230 +vt 0.045807 0.520230 +vt 0.046774 0.479770 +vt 0.048060 0.500000 +vt 0.049737 0.479770 +vt 0.050965 0.500000 +vt 0.054787 0.479770 +vt 0.055922 0.500000 +vt 0.062016 0.479770 +vt 0.063022 0.500000 +vt 0.071517 0.479770 +vt 0.072360 0.500000 +vt 0.083382 0.479770 +vt 0.084029 0.500000 +vt 0.097701 0.479770 +vt 0.098121 0.500000 +vt 0.114567 0.479770 +vt 0.114731 0.500000 +vt 0.134073 0.479770 +vt 0.133951 0.500000 +vt 0.156309 0.479770 +vt 0.043284 0.464036 +vt 0.046402 0.464036 +vt 0.051707 0.464036 +vt 0.059286 0.464036 +vt 0.069229 0.464036 +vt 0.081624 0.464036 +vt 0.096560 0.464036 +vt 0.114123 0.464036 +vt 0.134404 0.464036 +vt 0.157490 0.464036 +vt 0.038141 0.452797 +vt 0.041488 0.452797 +vt 0.047167 0.452797 +vt 0.055263 0.452797 +vt 0.065858 0.452797 +vt 0.079035 0.452797 +vt 0.094878 0.452797 +vt 0.113469 0.452797 +vt 0.134892 0.452797 +vt 0.159230 0.452797 +vt 0.031895 0.446054 +vt 0.035520 0.446054 +vt 0.041655 0.446054 +vt 0.050377 0.446054 +vt 0.061764 0.446054 +vt 0.075891 0.446054 +vt 0.092835 0.446054 +vt 0.112675 0.446054 +vt 0.135485 0.446054 +vt 0.161343 0.446054 +vt 0.025099 0.443806 +vt 0.029026 0.443806 +vt 0.035657 0.443806 +vt 0.045061 0.443806 +vt 0.057308 0.443806 +vt 0.072469 0.443806 +vt 0.090613 0.443806 +vt 0.111810 0.443806 +vt 0.136130 0.443806 +vt 0.163643 0.443806 +vt 0.018303 0.446054 +vt 0.022532 0.446054 +vt 0.029659 0.446054 +vt 0.039744 0.446054 +vt 0.052853 0.446054 +vt 0.069047 0.446054 +vt 0.088390 0.446054 +vt 0.110945 0.446054 +vt 0.136775 0.446054 +vt 0.165942 0.446054 +vt 0.012058 0.452797 +vt 0.016565 0.452797 +vt 0.024146 0.452797 +vt 0.034859 0.452797 +vt 0.048759 0.452797 +vt 0.065903 0.452797 +vt 0.086348 0.452797 +vt 0.110151 0.452797 +vt 0.137368 0.452797 +vt 0.168055 0.452797 +vt 0.006915 0.464036 +vt 0.011651 0.464036 +vt 0.019607 0.464036 +vt 0.030836 0.464036 +vt 0.045387 0.464036 +vt 0.063314 0.464036 +vt 0.084666 0.464036 +vt 0.109496 0.464036 +vt 0.137856 0.464036 +vt 0.169796 0.464036 +vt 0.003425 0.479770 +vt 0.008316 0.479770 +vt 0.016527 0.479770 +vt 0.028105 0.479770 +vt 0.043099 0.479770 +vt 0.061556 0.479770 +vt 0.083525 0.479770 +vt 0.109052 0.479770 +vt 0.138187 0.479770 +vt 0.170976 0.479770 +vt 0.002139 0.500000 +vt 0.007087 0.500000 +vt 0.015392 0.500000 +vt 0.027100 0.500000 +vt 0.042256 0.500000 +vt 0.060909 0.500000 +vt 0.083104 0.500000 +vt 0.108889 0.500000 +vt 0.138309 0.500000 +vt 0.171412 0.500000 +vt 0.003425 0.520230 +vt 0.008316 0.520230 +vt 0.016527 0.520230 +vt 0.028105 0.520230 +vt 0.043099 0.520230 +vt 0.061556 0.520230 +vt 0.083525 0.520230 +vt 0.109052 0.520230 +vt 0.138187 0.520230 +vt 0.170977 0.520230 +vt 0.006915 0.535964 +vt 0.011651 0.535964 +vt 0.019607 0.535964 +vt 0.030836 0.535964 +vt 0.045387 0.535964 +vt 0.063314 0.535964 +vt 0.084666 0.535964 +vt 0.109496 0.535964 +vt 0.137856 0.535964 +vt 0.169796 0.535964 +vt 0.012058 0.547203 +vt 0.016565 0.547203 +vt 0.024146 0.547203 +vt 0.034859 0.547203 +vt 0.048759 0.547203 +vt 0.065903 0.547203 +vt 0.086348 0.547203 +vt 0.110151 0.547203 +vt 0.137368 0.547203 +vt 0.168055 0.547203 +vt 0.018303 0.553946 +vt 0.022532 0.553946 +vt 0.029659 0.553946 +vt 0.039744 0.553946 +vt 0.052853 0.553946 +vt 0.069047 0.553946 +vt 0.088390 0.553946 +vt 0.110945 0.553946 +vt 0.136775 0.553946 +vt 0.165942 0.553946 +vt 0.025099 0.556194 +vt 0.029026 0.556194 +vt 0.035657 0.556194 +vt 0.045061 0.556194 +vt 0.057308 0.556194 +vt 0.072469 0.556194 +vt 0.090613 0.556194 +vt 0.111810 0.556194 +vt 0.136130 0.556194 +vt 0.163643 0.556194 +vt 0.031895 0.553946 +vt 0.035520 0.553946 +vt 0.041655 0.553946 +vt 0.050377 0.553946 +vt 0.061764 0.553946 +vt 0.075891 0.553946 +vt 0.092835 0.553946 +vt 0.112675 0.553946 +vt 0.135485 0.553946 +vt 0.161343 0.553946 +vt 0.038141 0.547203 +vt 0.041488 0.547203 +vt 0.047167 0.547203 +vt 0.055263 0.547203 +vt 0.065858 0.547203 +vt 0.079035 0.547203 +vt 0.094878 0.547203 +vt 0.113469 0.547203 +vt 0.134892 0.547203 +vt 0.159230 0.547203 +vt 0.043284 0.535964 +vt 0.046402 0.535964 +vt 0.051707 0.535964 +vt 0.059286 0.535964 +vt 0.069229 0.535964 +vt 0.081624 0.535964 +vt 0.096560 0.535964 +vt 0.114123 0.535964 +vt 0.134404 0.535964 +vt 0.157490 0.535964 +vt 0.046774 0.520230 +vt 0.049737 0.520230 +vt 0.054787 0.520230 +vt 0.062016 0.520230 +vt 0.071517 0.520230 +vt 0.083382 0.520230 +vt 0.097701 0.520230 +vt 0.114567 0.520230 +vt 0.134073 0.520230 +vt 0.156309 0.520230 +vt 0.730760 0.500000 +vt 0.730760 0.455494 +vt 0.767956 0.456269 +vt 0.767413 0.500000 +vt 0.795548 0.458370 +vt 0.794650 0.500000 +vt 0.815355 0.461466 +vt 0.814243 0.500000 +vt 0.829194 0.465226 +vt 0.827962 0.500000 +vt 0.838885 0.469318 +vt 0.837580 0.500000 +vt 0.846245 0.473410 +vt 0.844867 0.500000 +vt 0.853093 0.477170 +vt 0.851595 0.500000 +vt 0.861247 0.480266 +vt 0.859535 0.500000 +vt 0.872525 0.482368 +vt 0.870457 0.500000 +vt 0.888745 0.483142 +vt 0.886135 0.500000 +vt 0.730760 0.420879 +vt 0.769430 0.422255 +vt 0.797985 0.425991 +vt 0.818373 0.431496 +vt 0.832539 0.438180 +vt 0.842428 0.445454 +vt 0.849986 0.452729 +vt 0.857160 0.459413 +vt 0.865895 0.464918 +vt 0.878136 0.468654 +vt 0.895830 0.470030 +vt 0.730760 0.396154 +vt 0.771601 0.397960 +vt 0.801577 0.402863 +vt 0.822821 0.410088 +vt 0.837467 0.418861 +vt 0.847648 0.428409 +vt 0.855499 0.437956 +vt 0.863153 0.446730 +vt 0.872744 0.453955 +vt 0.886405 0.458858 +vt 0.906271 0.460664 +vt 0.730760 0.381319 +vt 0.774238 0.383383 +vt 0.805938 0.388986 +vt 0.828222 0.397243 +vt 0.843451 0.407270 +vt 0.853988 0.418182 +vt 0.862193 0.429093 +vt 0.870431 0.439120 +vt 0.881061 0.447377 +vt 0.896447 0.452980 +vt 0.918950 0.455045 +vt 0.730760 0.376374 +vt 0.777108 0.378524 +vt 0.810685 0.384361 +vt 0.834100 0.392962 +vt 0.849963 0.403406 +vt 0.860886 0.414773 +vt 0.869478 0.426139 +vt 0.878350 0.436583 +vt 0.890112 0.445185 +vt 0.907374 0.451021 +vt 0.932747 0.453172 +vt 0.779978 0.383383 +vt 0.815431 0.388986 +vt 0.839977 0.397243 +vt 0.856476 0.407270 +vt 0.867785 0.418182 +vt 0.876763 0.429093 +vt 0.886270 0.439120 +vt 0.899163 0.447377 +vt 0.918302 0.452980 +vt 0.946544 0.455045 +vt 0.782615 0.397960 +vt 0.819792 0.402863 +vt 0.845378 0.410088 +vt 0.862460 0.418861 +vt 0.874124 0.428409 +vt 0.883458 0.437956 +vt 0.893547 0.446730 +vt 0.907480 0.453955 +vt 0.928343 0.458858 +vt 0.959223 0.460664 +vt 0.784787 0.422255 +vt 0.823384 0.425991 +vt 0.849826 0.431496 +vt 0.867388 0.438180 +vt 0.879345 0.445454 +vt 0.888971 0.452729 +vt 0.899541 0.459413 +vt 0.914330 0.464918 +vt 0.936613 0.468654 +vt 0.969664 0.470030 +vt 0.786261 0.456269 +vt 0.825821 0.458370 +vt 0.852845 0.461466 +vt 0.870732 0.465226 +vt 0.882887 0.469318 +vt 0.892712 0.473410 +vt 0.903608 0.477170 +vt 0.918978 0.480266 +vt 0.942224 0.482368 +vt 0.976749 0.483142 +vt 0.786804 0.500000 +vt 0.826719 0.500000 +vt 0.853957 0.500000 +vt 0.871964 0.500000 +vt 0.884192 0.500000 +vt 0.894090 0.500000 +vt 0.905106 0.500000 +vt 0.920690 0.500000 +vt 0.944291 0.500000 +vt 0.979359 0.500000 +vt 0.730760 0.544505 +vt 0.786261 0.543731 +vt 0.825821 0.541630 +vt 0.852845 0.538534 +vt 0.870732 0.534773 +vt 0.882887 0.530682 +vt 0.892712 0.526590 +vt 0.903608 0.522830 +vt 0.918978 0.519733 +vt 0.942224 0.517632 +vt 0.976749 0.516858 +vt 0.730760 0.579121 +vt 0.784787 0.577745 +vt 0.823384 0.574009 +vt 0.849826 0.568504 +vt 0.867388 0.561820 +vt 0.879345 0.554545 +vt 0.888971 0.547271 +vt 0.899541 0.540586 +vt 0.914330 0.535082 +vt 0.936613 0.531346 +vt 0.969664 0.529970 +vt 0.730760 0.603846 +vt 0.782615 0.602040 +vt 0.819792 0.597137 +vt 0.845378 0.589912 +vt 0.862460 0.581138 +vt 0.874124 0.571591 +vt 0.883458 0.562043 +vt 0.893547 0.553270 +vt 0.907480 0.546045 +vt 0.928343 0.541142 +vt 0.959223 0.539335 +vt 0.730760 0.618681 +vt 0.779978 0.616617 +vt 0.815431 0.611014 +vt 0.839977 0.602756 +vt 0.856476 0.592730 +vt 0.867785 0.581818 +vt 0.876763 0.570907 +vt 0.886270 0.560880 +vt 0.899163 0.552622 +vt 0.918302 0.547019 +vt 0.946544 0.544955 +vt 0.730760 0.623626 +vt 0.777108 0.621476 +vt 0.810685 0.615639 +vt 0.834100 0.607038 +vt 0.849963 0.596593 +vt 0.860886 0.585227 +vt 0.869478 0.573861 +vt 0.878350 0.563416 +vt 0.890112 0.554815 +vt 0.907374 0.548978 +vt 0.932747 0.546828 +vt 0.774238 0.616617 +vt 0.805938 0.611014 +vt 0.828222 0.602756 +vt 0.843451 0.592730 +vt 0.853988 0.581818 +vt 0.862194 0.570907 +vt 0.870431 0.560880 +vt 0.881061 0.552622 +vt 0.896447 0.547019 +vt 0.918950 0.544955 +vt 0.771601 0.602040 +vt 0.801577 0.597137 +vt 0.822821 0.589912 +vt 0.837467 0.581138 +vt 0.847648 0.571591 +vt 0.855499 0.562043 +vt 0.863153 0.553270 +vt 0.872744 0.546045 +vt 0.886405 0.541142 +vt 0.906271 0.539335 +vt 0.769430 0.577745 +vt 0.797985 0.574009 +vt 0.818373 0.568504 +vt 0.832539 0.561820 +vt 0.842428 0.554545 +vt 0.849986 0.547271 +vt 0.857160 0.540586 +vt 0.865895 0.535082 +vt 0.878136 0.531346 +vt 0.895830 0.529970 +vt 0.767956 0.543731 +vt 0.795548 0.541630 +vt 0.815355 0.538534 +vt 0.829194 0.534773 +vt 0.838885 0.530682 +vt 0.846245 0.526590 +vt 0.853093 0.522830 +vt 0.861247 0.519733 +vt 0.872525 0.517632 +vt 0.888745 0.516858 +vt 0.893500 0.483330 +vt 0.890765 0.500000 +vt 0.898000 0.483843 +vt 0.895208 0.500000 +vt 0.902065 0.484598 +vt 0.899279 0.500000 +vt 0.905518 0.485515 +vt 0.902791 0.500000 +vt 0.908180 0.486513 +vt 0.905556 0.500000 +vt 0.909875 0.487511 +vt 0.907390 0.500000 +vt 0.910423 0.488428 +vt 0.908105 0.500000 +vt 0.909648 0.489184 +vt 0.907514 0.500000 +vt 0.907370 0.489696 +vt 0.905432 0.500000 +vt 0.903412 0.489885 +vt 0.901672 0.500000 +vt 0.900926 0.470365 +vt 0.905576 0.471277 +vt 0.909625 0.472619 +vt 0.912919 0.474250 +vt 0.915302 0.476024 +vt 0.916620 0.477798 +vt 0.916717 0.479428 +vt 0.915438 0.480771 +vt 0.912630 0.481682 +vt 0.908136 0.482018 +vt 0.911869 0.461105 +vt 0.916741 0.462301 +vt 0.920767 0.464063 +vt 0.923827 0.466203 +vt 0.925798 0.468531 +vt 0.926560 0.470860 +vt 0.925992 0.473000 +vt 0.923972 0.474762 +vt 0.920381 0.475958 +vt 0.915096 0.476398 +vt 0.925156 0.455548 +vt 0.930299 0.456915 +vt 0.934297 0.458929 +vt 0.937072 0.461374 +vt 0.938542 0.464036 +vt 0.938630 0.466697 +vt 0.937254 0.469143 +vt 0.934335 0.471157 +vt 0.929793 0.472523 +vt 0.923549 0.473027 +vt 0.939616 0.453696 +vt 0.945053 0.455120 +vt 0.949021 0.457218 +vt 0.951485 0.459765 +vt 0.952412 0.462537 +vt 0.951765 0.465310 +vt 0.949510 0.467857 +vt 0.945612 0.469955 +vt 0.940036 0.471378 +vt 0.932747 0.471903 +vt 0.954077 0.455548 +vt 0.959807 0.456915 +vt 0.963744 0.458929 +vt 0.965899 0.461374 +vt 0.966281 0.464036 +vt 0.964900 0.466697 +vt 0.961766 0.469143 +vt 0.956889 0.471157 +vt 0.950279 0.472523 +vt 0.941945 0.473027 +vt 0.967364 0.461105 +vt 0.973364 0.462301 +vt 0.977274 0.464063 +vt 0.979144 0.466203 +vt 0.979025 0.468531 +vt 0.976970 0.470860 +vt 0.973028 0.473000 +vt 0.967252 0.474762 +vt 0.959691 0.475958 +vt 0.950397 0.476398 +vt 0.978307 0.470365 +vt 0.984529 0.471277 +vt 0.988416 0.472619 +vt 0.990051 0.474250 +vt 0.989521 0.476024 +vt 0.986910 0.477798 +vt 0.982303 0.479428 +vt 0.975785 0.480771 +vt 0.967442 0.481682 +vt 0.957358 0.482018 +vt 0.985732 0.483330 +vt 0.992105 0.483843 +vt 0.995976 0.484598 +vt 0.997453 0.485515 +vt 0.996643 0.486513 +vt 0.993655 0.487511 +vt 0.988597 0.488428 +vt 0.981576 0.489184 +vt 0.972702 0.489696 +vt 0.962082 0.489885 +vt 0.988468 0.500000 +vt 0.994897 0.500000 +vt 0.998762 0.500000 +vt 1.000180 0.500000 +vt 0.999267 0.500000 +vt 0.996140 0.500000 +vt 0.990915 0.500000 +vt 0.983710 0.500000 +vt 0.974640 0.500000 +vt 0.963822 0.500000 +vt 0.985732 0.516669 +vt 0.992105 0.516157 +vt 0.995976 0.515401 +vt 0.997453 0.514484 +vt 0.996643 0.513486 +vt 0.993655 0.512488 +vt 0.988597 0.511571 +vt 0.981576 0.510816 +vt 0.972702 0.510303 +vt 0.962082 0.510115 +vt 0.978307 0.529634 +vt 0.984529 0.528723 +vt 0.988416 0.527380 +vt 0.990051 0.525750 +vt 0.989521 0.523976 +vt 0.986910 0.522202 +vt 0.982303 0.520571 +vt 0.975785 0.519229 +vt 0.967442 0.518317 +vt 0.957358 0.517982 +vt 0.967364 0.538895 +vt 0.973364 0.537699 +vt 0.977274 0.535937 +vt 0.979144 0.533797 +vt 0.979025 0.531468 +vt 0.976970 0.529140 +vt 0.973028 0.527000 +vt 0.967252 0.525238 +vt 0.959691 0.524042 +vt 0.950397 0.523601 +vt 0.954077 0.544451 +vt 0.959806 0.543085 +vt 0.963744 0.541071 +vt 0.965899 0.538625 +vt 0.966281 0.535964 +vt 0.964900 0.533302 +vt 0.961766 0.530857 +vt 0.956889 0.528843 +vt 0.950279 0.527476 +vt 0.941945 0.526973 +vt 0.939616 0.546304 +vt 0.945053 0.544880 +vt 0.949021 0.542782 +vt 0.951485 0.540235 +vt 0.952412 0.537462 +vt 0.951765 0.534690 +vt 0.949510 0.532143 +vt 0.945612 0.530045 +vt 0.940036 0.528621 +vt 0.932747 0.528097 +vt 0.925156 0.544451 +vt 0.930299 0.543085 +vt 0.934297 0.541071 +vt 0.937072 0.538625 +vt 0.938542 0.535964 +vt 0.938630 0.533303 +vt 0.937254 0.530857 +vt 0.934335 0.528843 +vt 0.929793 0.527476 +vt 0.923549 0.526973 +vt 0.911869 0.538895 +vt 0.916741 0.537699 +vt 0.920768 0.535937 +vt 0.923827 0.533797 +vt 0.925798 0.531468 +vt 0.926560 0.529140 +vt 0.925992 0.527000 +vt 0.923972 0.525238 +vt 0.920381 0.524042 +vt 0.915096 0.523601 +vt 0.900926 0.529634 +vt 0.905576 0.528723 +vt 0.909625 0.527380 +vt 0.912919 0.525750 +vt 0.915302 0.523976 +vt 0.916620 0.522202 +vt 0.916717 0.520571 +vt 0.915438 0.519229 +vt 0.912630 0.518318 +vt 0.908136 0.517982 +vt 0.893500 0.516669 +vt 0.898000 0.516157 +vt 0.902065 0.515401 +vt 0.905518 0.514484 +vt 0.908180 0.513486 +vt 0.909875 0.512488 +vt 0.910423 0.511571 +vt 0.909648 0.510816 +vt 0.907370 0.510303 +vt 0.903412 0.510115 +vt 0.496472 0.491996 +vt 0.496859 0.500000 +vt 0.513988 0.487299 +vt 0.514603 0.500000 +vt 0.521565 0.485268 +vt 0.522278 0.500000 +vt 0.521596 0.485261 +vt 0.522309 0.500000 +vt 0.516473 0.486636 +vt 0.517120 0.500000 +vt 0.508589 0.488753 +vt 0.509134 0.500000 +vt 0.500336 0.490970 +vt 0.500775 0.500000 +vt 0.494109 0.492645 +vt 0.494466 0.500000 +vt 0.492298 0.493137 +vt 0.492633 0.500000 +vt 0.497298 0.491804 +vt 0.497698 0.500000 +vt 0.495347 0.484448 +vt 0.512204 0.475321 +vt 0.519495 0.471374 +vt 0.519525 0.471360 +vt 0.514594 0.474032 +vt 0.507007 0.478144 +vt 0.499064 0.482450 +vt 0.493070 0.485703 +vt 0.491327 0.486656 +vt 0.496137 0.484064 +vt 0.493543 0.477446 +vt 0.509340 0.464211 +vt 0.516173 0.458486 +vt 0.516200 0.458465 +vt 0.511579 0.462339 +vt 0.504468 0.468302 +vt 0.497024 0.474544 +vt 0.491405 0.479259 +vt 0.489769 0.480639 +vt 0.494275 0.476875 +vt 0.491114 0.471082 +vt 0.505487 0.454112 +vt 0.511703 0.446772 +vt 0.511728 0.446745 +vt 0.507523 0.451711 +vt 0.501052 0.459354 +vt 0.494278 0.467356 +vt 0.489165 0.473398 +vt 0.487675 0.475163 +vt 0.491772 0.470334 +vt 0.488119 0.465447 +vt 0.500734 0.445171 +vt 0.506190 0.436400 +vt 0.506211 0.436367 +vt 0.502520 0.442300 +vt 0.496839 0.451431 +vt 0.490893 0.460989 +vt 0.486403 0.468206 +vt 0.485093 0.470311 +vt 0.488686 0.464535 +vt 0.484614 0.460633 +vt 0.495171 0.437531 +vt 0.499737 0.427538 +vt 0.499754 0.427499 +vt 0.496665 0.434258 +vt 0.491910 0.444659 +vt 0.486932 0.455547 +vt 0.483173 0.463767 +vt 0.482075 0.466162 +vt 0.485079 0.459576 +vt 0.480655 0.456730 +vt 0.488889 0.431337 +vt 0.492450 0.420353 +vt 0.492463 0.420309 +vt 0.490053 0.427737 +vt 0.486343 0.439169 +vt 0.482460 0.451134 +vt 0.479527 0.460166 +vt 0.478668 0.462795 +vt 0.481010 0.455552 +vt 0.476299 0.453829 +vt 0.481976 0.426733 +vt 0.484432 0.415013 +vt 0.484441 0.414966 +vt 0.482778 0.422891 +vt 0.480220 0.435088 +vt 0.477541 0.447854 +vt 0.475518 0.457489 +vt 0.474925 0.460291 +vt 0.476537 0.452559 +vt 0.471603 0.452021 +vt 0.474525 0.423865 +vt 0.475788 0.411686 +vt 0.475793 0.411637 +vt 0.474937 0.419872 +vt 0.473620 0.432545 +vt 0.472241 0.445809 +vt 0.471199 0.455820 +vt 0.470893 0.458730 +vt 0.471722 0.450693 +vt 0.466623 0.451399 +vt 0.466623 0.422877 +vt 0.466623 0.410539 +vt 0.466623 0.410489 +vt 0.466623 0.418831 +vt 0.466623 0.431668 +vt 0.466623 0.445105 +vt 0.466623 0.455245 +vt 0.466623 0.458192 +vt 0.466623 0.450050 +vt 0.461644 0.452021 +vt 0.458722 0.423865 +vt 0.457458 0.411686 +vt 0.457454 0.411637 +vt 0.458309 0.419872 +vt 0.459626 0.432545 +vt 0.461005 0.445809 +vt 0.462047 0.455820 +vt 0.462354 0.458730 +vt 0.461524 0.450693 +vt 0.456948 0.453829 +vt 0.451270 0.426733 +vt 0.448814 0.415013 +vt 0.448806 0.414966 +vt 0.450468 0.422891 +vt 0.453026 0.435088 +vt 0.455705 0.447854 +vt 0.457729 0.457489 +vt 0.458322 0.460291 +vt 0.456709 0.452559 +vt 0.452592 0.456730 +vt 0.444358 0.431337 +vt 0.440797 0.420353 +vt 0.440784 0.420309 +vt 0.443194 0.427737 +vt 0.446903 0.439169 +vt 0.450787 0.451134 +vt 0.453720 0.460166 +vt 0.454578 0.462795 +vt 0.452237 0.455552 +vt 0.448633 0.460633 +vt 0.438076 0.437531 +vt 0.433509 0.427538 +vt 0.433492 0.427499 +vt 0.436582 0.434258 +vt 0.441337 0.444659 +vt 0.446315 0.455547 +vt 0.450074 0.463767 +vt 0.451172 0.466162 +vt 0.448167 0.459576 +vt 0.445127 0.465447 +vt 0.432513 0.445171 +vt 0.427057 0.436400 +vt 0.427036 0.436367 +vt 0.430727 0.442300 +vt 0.436407 0.451431 +vt 0.442354 0.460989 +vt 0.446843 0.468206 +vt 0.448153 0.470311 +vt 0.444560 0.464535 +vt 0.442132 0.471082 +vt 0.427760 0.454112 +vt 0.421543 0.446772 +vt 0.421519 0.446745 +vt 0.425724 0.451711 +vt 0.432195 0.459354 +vt 0.438968 0.467356 +vt 0.444082 0.473398 +vt 0.445572 0.475163 +vt 0.441475 0.470334 +vt 0.439704 0.477446 +vt 0.423906 0.464211 +vt 0.417073 0.458486 +vt 0.417046 0.458465 +vt 0.421667 0.462340 +vt 0.428779 0.468302 +vt 0.436223 0.474544 +vt 0.441842 0.479259 +vt 0.443477 0.480639 +vt 0.438972 0.476875 +vt 0.437899 0.484448 +vt 0.421043 0.475321 +vt 0.413751 0.471374 +vt 0.413722 0.471360 +vt 0.418652 0.474032 +vt 0.426240 0.478144 +vt 0.434182 0.482450 +vt 0.440176 0.485703 +vt 0.441919 0.486656 +vt 0.437110 0.484064 +vt 0.436775 0.491996 +vt 0.419258 0.487299 +vt 0.411681 0.485268 +vt 0.411651 0.485261 +vt 0.416774 0.486636 +vt 0.424658 0.488753 +vt 0.432910 0.490970 +vt 0.439138 0.492645 +vt 0.440948 0.493137 +vt 0.435949 0.491804 +vt 0.436387 0.500000 +vt 0.418644 0.500000 +vt 0.410968 0.500000 +vt 0.410937 0.500000 +vt 0.416126 0.500000 +vt 0.424113 0.500000 +vt 0.432472 0.500000 +vt 0.438780 0.500000 +vt 0.440614 0.500000 +vt 0.435548 0.500000 +vt 0.436775 0.508004 +vt 0.419258 0.512701 +vt 0.411681 0.514732 +vt 0.411651 0.514739 +vt 0.416774 0.513364 +vt 0.424658 0.511247 +vt 0.432910 0.509030 +vt 0.439138 0.507355 +vt 0.440948 0.506863 +vt 0.435949 0.508196 +vt 0.437899 0.515552 +vt 0.421043 0.524679 +vt 0.413751 0.528626 +vt 0.413722 0.528640 +vt 0.418652 0.525968 +vt 0.426240 0.521856 +vt 0.434182 0.517550 +vt 0.440176 0.514297 +vt 0.441919 0.513344 +vt 0.437110 0.515936 +vt 0.439704 0.522554 +vt 0.423906 0.535789 +vt 0.417073 0.541514 +vt 0.417046 0.541535 +vt 0.421667 0.537661 +vt 0.428779 0.531698 +vt 0.436223 0.525456 +vt 0.441842 0.520741 +vt 0.443477 0.519361 +vt 0.438972 0.523125 +vt 0.442132 0.528918 +vt 0.427760 0.545888 +vt 0.421543 0.553228 +vt 0.421519 0.553255 +vt 0.425724 0.548289 +vt 0.432195 0.540646 +vt 0.438968 0.532644 +vt 0.444082 0.526602 +vt 0.445572 0.524837 +vt 0.441475 0.529666 +vt 0.445127 0.534553 +vt 0.432513 0.554829 +vt 0.427057 0.563600 +vt 0.427036 0.563633 +vt 0.430727 0.557700 +vt 0.436407 0.548569 +vt 0.442354 0.539011 +vt 0.446843 0.531794 +vt 0.448153 0.529689 +vt 0.444560 0.535465 +vt 0.448633 0.539367 +vt 0.438076 0.562469 +vt 0.433509 0.572462 +vt 0.433492 0.572501 +vt 0.436582 0.565742 +vt 0.441337 0.555341 +vt 0.446315 0.544453 +vt 0.450074 0.536233 +vt 0.451172 0.533838 +vt 0.448167 0.540424 +vt 0.452592 0.543270 +vt 0.444358 0.568663 +vt 0.440797 0.579647 +vt 0.440784 0.579691 +vt 0.443194 0.572263 +vt 0.446903 0.560831 +vt 0.450787 0.548866 +vt 0.453720 0.539834 +vt 0.454578 0.537205 +vt 0.452237 0.544448 +vt 0.456948 0.546171 +vt 0.451270 0.573267 +vt 0.448814 0.584987 +vt 0.448806 0.585034 +vt 0.450468 0.577109 +vt 0.453026 0.564912 +vt 0.455705 0.552146 +vt 0.457729 0.542511 +vt 0.458322 0.539709 +vt 0.456709 0.547441 +vt 0.461644 0.547979 +vt 0.458722 0.576135 +vt 0.457458 0.588314 +vt 0.457454 0.588363 +vt 0.458309 0.580128 +vt 0.459626 0.567455 +vt 0.461005 0.554191 +vt 0.462048 0.544180 +vt 0.462354 0.541270 +vt 0.461524 0.549307 +vt 0.466623 0.548601 +vt 0.466623 0.577123 +vt 0.466623 0.589461 +vt 0.466623 0.589511 +vt 0.466623 0.581169 +vt 0.466623 0.568332 +vt 0.466623 0.554895 +vt 0.466623 0.544755 +vt 0.466623 0.541808 +vt 0.466623 0.549950 +vt 0.471603 0.547979 +vt 0.474525 0.576135 +vt 0.475788 0.588314 +vt 0.475793 0.588363 +vt 0.474937 0.580128 +vt 0.473620 0.567455 +vt 0.472241 0.554191 +vt 0.471199 0.544180 +vt 0.470893 0.541270 +vt 0.471722 0.549307 +vt 0.476299 0.546171 +vt 0.481977 0.573267 +vt 0.484432 0.584987 +vt 0.484441 0.585034 +vt 0.482778 0.577109 +vt 0.480220 0.564912 +vt 0.477541 0.552146 +vt 0.475518 0.542511 +vt 0.474925 0.539709 +vt 0.476537 0.547441 +vt 0.480655 0.543270 +vt 0.488889 0.568663 +vt 0.492450 0.579647 +vt 0.492463 0.579691 +vt 0.490053 0.572263 +vt 0.486343 0.560831 +vt 0.482460 0.548866 +vt 0.479527 0.539834 +vt 0.478668 0.537205 +vt 0.481010 0.544448 +vt 0.484614 0.539367 +vt 0.495171 0.562469 +vt 0.499737 0.572462 +vt 0.499754 0.572501 +vt 0.496665 0.565742 +vt 0.491910 0.555341 +vt 0.486932 0.544453 +vt 0.483173 0.536233 +vt 0.482075 0.533838 +vt 0.485079 0.540424 +vt 0.488119 0.534553 +vt 0.500734 0.554829 +vt 0.506190 0.563600 +vt 0.506211 0.563633 +vt 0.502520 0.557700 +vt 0.496839 0.548569 +vt 0.490893 0.539011 +vt 0.486403 0.531794 +vt 0.485093 0.529689 +vt 0.488686 0.535465 +vt 0.491114 0.528918 +vt 0.505487 0.545888 +vt 0.511703 0.553228 +vt 0.511728 0.553255 +vt 0.507523 0.548289 +vt 0.501052 0.540646 +vt 0.494278 0.532644 +vt 0.489165 0.526602 +vt 0.487675 0.524837 +vt 0.491772 0.529666 +vt 0.493543 0.522554 +vt 0.509340 0.535789 +vt 0.516173 0.541514 +vt 0.516200 0.541535 +vt 0.511579 0.537660 +vt 0.504468 0.531698 +vt 0.497024 0.525456 +vt 0.491405 0.520741 +vt 0.489769 0.519361 +vt 0.494275 0.523125 +vt 0.495347 0.515552 +vt 0.512204 0.524679 +vt 0.519495 0.528626 +vt 0.519525 0.528640 +vt 0.514594 0.525968 +vt 0.507007 0.521856 +vt 0.499064 0.517550 +vt 0.493070 0.514297 +vt 0.491327 0.513344 +vt 0.496137 0.515936 +vt 0.496472 0.508004 +vt 0.513988 0.512701 +vt 0.521565 0.514732 +vt 0.521596 0.514739 +vt 0.516473 0.513364 +vt 0.508589 0.511247 +vt 0.500336 0.509030 +vt 0.494109 0.507355 +vt 0.492298 0.506863 +vt 0.497298 0.508196 +vt 0.509476 0.488550 +vt 0.510035 0.500000 +vt 0.526623 0.483969 +vt 0.527406 0.500000 +vt 0.547267 0.478453 +vt 0.548319 0.500000 +vt 0.569936 0.472396 +vt 0.571284 0.500000 +vt 0.593156 0.466192 +vt 0.594807 0.500000 +vt 0.615457 0.460234 +vt 0.617399 0.500000 +vt 0.635365 0.454915 +vt 0.637566 0.500000 +vt 0.651407 0.450628 +vt 0.653819 0.500000 +vt 0.662113 0.447768 +vt 0.664664 0.500000 +vt 0.666009 0.446727 +vt 0.668610 0.500000 +vt 0.507854 0.477737 +vt 0.524352 0.468829 +vt 0.544215 0.458104 +vt 0.566025 0.446327 +vt 0.588367 0.434264 +vt 0.609824 0.422678 +vt 0.628978 0.412336 +vt 0.644414 0.404001 +vt 0.654714 0.398439 +vt 0.658462 0.396415 +vt 0.505253 0.467695 +vt 0.520710 0.454768 +vt 0.539320 0.439205 +vt 0.559754 0.422115 +vt 0.580687 0.404610 +vt 0.600789 0.387798 +vt 0.618735 0.372790 +vt 0.633197 0.360696 +vt 0.642848 0.352625 +vt 0.646359 0.349688 +vt 0.501755 0.458556 +vt 0.515813 0.441973 +vt 0.532738 0.422007 +vt 0.551323 0.400084 +vt 0.570360 0.377626 +vt 0.588643 0.356059 +vt 0.604964 0.336805 +vt 0.618117 0.321290 +vt 0.626893 0.310936 +vt 0.630087 0.307169 +vt 0.497446 0.450456 +vt 0.509779 0.430631 +vt 0.524627 0.406764 +vt 0.540932 0.380555 +vt 0.557634 0.353709 +vt 0.573674 0.327926 +vt 0.587993 0.304910 +vt 0.599532 0.286362 +vt 0.607232 0.273984 +vt 0.610034 0.269480 +vt 0.492406 0.443528 +vt 0.502723 0.420931 +vt 0.515144 0.393726 +vt 0.528783 0.363853 +vt 0.542754 0.333253 +vt 0.556172 0.303865 +vt 0.568150 0.277630 +vt 0.577802 0.256488 +vt 0.584243 0.242380 +vt 0.586587 0.237247 +vt 0.486721 0.437907 +vt 0.494763 0.413061 +vt 0.504445 0.383147 +vt 0.515077 0.350301 +vt 0.525967 0.316654 +vt 0.536426 0.284340 +vt 0.545763 0.255494 +vt 0.553287 0.232248 +vt 0.558308 0.216736 +vt 0.560135 0.211091 +vt 0.480473 0.433726 +vt 0.486015 0.407206 +vt 0.492687 0.375279 +vt 0.500014 0.340220 +vt 0.507519 0.304308 +vt 0.514727 0.269818 +vt 0.521161 0.239029 +vt 0.526346 0.214218 +vt 0.529806 0.197661 +vt 0.531065 0.191636 +vt 0.473746 0.431119 +vt 0.476596 0.403556 +vt 0.480028 0.370373 +vt 0.483796 0.333935 +vt 0.487656 0.296610 +vt 0.491362 0.260764 +vt 0.494672 0.228764 +vt 0.497338 0.202976 +vt 0.499118 0.185768 +vt 0.499765 0.179507 +vt 0.466623 0.430220 +vt 0.466623 0.402298 +vt 0.466623 0.368681 +vt 0.466623 0.331768 +vt 0.466623 0.293956 +vt 0.466623 0.257642 +vt 0.466623 0.225225 +vt 0.466623 0.199101 +vt 0.466623 0.181668 +vt 0.466623 0.175325 +vt 0.459500 0.431119 +vt 0.456650 0.403556 +vt 0.453219 0.370373 +vt 0.449451 0.333935 +vt 0.445591 0.296610 +vt 0.441884 0.260764 +vt 0.438575 0.228764 +vt 0.435908 0.202976 +vt 0.434129 0.185768 +vt 0.433481 0.179507 +vt 0.452773 0.433726 +vt 0.447231 0.407206 +vt 0.440559 0.375279 +vt 0.433232 0.340220 +vt 0.425727 0.304308 +vt 0.418520 0.269818 +vt 0.412086 0.239030 +vt 0.406900 0.214218 +vt 0.403440 0.197661 +vt 0.402181 0.191636 +vt 0.446525 0.437907 +vt 0.438483 0.413061 +vt 0.428801 0.383147 +vt 0.418170 0.350301 +vt 0.407279 0.316654 +vt 0.396820 0.284341 +vt 0.387483 0.255494 +vt 0.379959 0.232248 +vt 0.374938 0.216736 +vt 0.373111 0.211091 +vt 0.440840 0.443528 +vt 0.430523 0.420932 +vt 0.418102 0.393726 +vt 0.404463 0.363853 +vt 0.390492 0.333253 +vt 0.377075 0.303865 +vt 0.365097 0.277630 +vt 0.355444 0.256488 +vt 0.349003 0.242381 +vt 0.346659 0.237247 +vt 0.435801 0.450456 +vt 0.423468 0.430631 +vt 0.408619 0.406764 +vt 0.392314 0.380555 +vt 0.375613 0.353709 +vt 0.359573 0.327926 +vt 0.345254 0.304910 +vt 0.333715 0.286362 +vt 0.326015 0.273985 +vt 0.323213 0.269481 +vt 0.431491 0.458556 +vt 0.417433 0.441973 +vt 0.400508 0.422007 +vt 0.381924 0.400084 +vt 0.362886 0.377626 +vt 0.344604 0.356059 +vt 0.328282 0.336806 +vt 0.315130 0.321290 +vt 0.306353 0.310937 +vt 0.303159 0.307169 +vt 0.427994 0.467695 +vt 0.412537 0.454768 +vt 0.393927 0.439205 +vt 0.373492 0.422115 +vt 0.352560 0.404610 +vt 0.332457 0.387798 +vt 0.314511 0.372790 +vt 0.300049 0.360696 +vt 0.290399 0.352625 +vt 0.286887 0.349688 +vt 0.425393 0.477737 +vt 0.408894 0.468829 +vt 0.389032 0.458104 +vt 0.367221 0.446327 +vt 0.344879 0.434264 +vt 0.323423 0.422678 +vt 0.304268 0.412336 +vt 0.288833 0.404001 +vt 0.278532 0.398440 +vt 0.274784 0.396416 +vt 0.423771 0.488550 +vt 0.406624 0.483969 +vt 0.385980 0.478453 +vt 0.363311 0.472397 +vt 0.340090 0.466192 +vt 0.317790 0.460234 +vt 0.297882 0.454915 +vt 0.281839 0.450629 +vt 0.271134 0.447768 +vt 0.267238 0.446727 +vt 0.423212 0.500000 +vt 0.405841 0.500000 +vt 0.384927 0.500000 +vt 0.361963 0.500000 +vt 0.338439 0.500000 +vt 0.315848 0.500000 +vt 0.295680 0.500000 +vt 0.279428 0.500000 +vt 0.268583 0.500000 +vt 0.264636 0.500000 +vt 0.423771 0.511450 +vt 0.406624 0.516031 +vt 0.385980 0.521547 +vt 0.363311 0.527604 +vt 0.340090 0.533808 +vt 0.317790 0.539766 +vt 0.297882 0.545085 +vt 0.281839 0.549372 +vt 0.271134 0.552232 +vt 0.267238 0.553273 +vt 0.425393 0.522263 +vt 0.408894 0.531171 +vt 0.389032 0.541896 +vt 0.367221 0.553673 +vt 0.344879 0.565736 +vt 0.323423 0.577322 +vt 0.304268 0.587664 +vt 0.288833 0.595999 +vt 0.278532 0.601561 +vt 0.274784 0.603584 +vt 0.427994 0.532305 +vt 0.412537 0.545232 +vt 0.393927 0.560795 +vt 0.373492 0.577885 +vt 0.352560 0.595390 +vt 0.332457 0.612202 +vt 0.314511 0.627210 +vt 0.300049 0.639304 +vt 0.290399 0.647375 +vt 0.286887 0.650312 +vt 0.431491 0.541444 +vt 0.417433 0.558027 +vt 0.400508 0.577993 +vt 0.381924 0.599916 +vt 0.362886 0.622374 +vt 0.344604 0.643941 +vt 0.328282 0.663195 +vt 0.315130 0.678710 +vt 0.306353 0.689064 +vt 0.303159 0.692831 +vt 0.435801 0.549544 +vt 0.423468 0.569369 +vt 0.408619 0.593236 +vt 0.392314 0.619445 +vt 0.375613 0.646291 +vt 0.359573 0.672074 +vt 0.345254 0.695090 +vt 0.333715 0.713638 +vt 0.326015 0.726016 +vt 0.323213 0.730520 +vt 0.440840 0.556472 +vt 0.430523 0.579069 +vt 0.418102 0.606274 +vt 0.404463 0.636147 +vt 0.390492 0.666747 +vt 0.377075 0.696135 +vt 0.365097 0.722370 +vt 0.355444 0.743512 +vt 0.349003 0.757620 +vt 0.346659 0.762753 +vt 0.446525 0.562093 +vt 0.438483 0.586939 +vt 0.428801 0.616853 +vt 0.418170 0.649699 +vt 0.407279 0.683346 +vt 0.396820 0.715660 +vt 0.387483 0.744506 +vt 0.379959 0.767752 +vt 0.374938 0.783264 +vt 0.373111 0.788909 +vt 0.452773 0.566274 +vt 0.447231 0.592794 +vt 0.440559 0.624721 +vt 0.433232 0.659780 +vt 0.425727 0.695692 +vt 0.418520 0.730182 +vt 0.412086 0.760971 +vt 0.406901 0.785782 +vt 0.403440 0.802339 +vt 0.402181 0.808364 +vt 0.459500 0.568881 +vt 0.456650 0.596444 +vt 0.453219 0.629627 +vt 0.449451 0.666065 +vt 0.445591 0.703390 +vt 0.441884 0.739236 +vt 0.438575 0.771236 +vt 0.435908 0.797024 +vt 0.434129 0.814232 +vt 0.433481 0.820493 +vt 0.466623 0.569780 +vt 0.466623 0.597702 +vt 0.466623 0.631319 +vt 0.466623 0.668232 +vt 0.466623 0.706044 +vt 0.466623 0.742358 +vt 0.466623 0.774775 +vt 0.466623 0.800899 +vt 0.466623 0.818332 +vt 0.466623 0.824675 +vt 0.473746 0.568881 +vt 0.476596 0.596444 +vt 0.480028 0.629627 +vt 0.483796 0.666065 +vt 0.487656 0.703390 +vt 0.491363 0.739236 +vt 0.494672 0.771236 +vt 0.497338 0.797024 +vt 0.499118 0.814232 +vt 0.499765 0.820493 +vt 0.480473 0.566274 +vt 0.486015 0.592794 +vt 0.492688 0.624721 +vt 0.500014 0.659780 +vt 0.507519 0.695692 +vt 0.514727 0.730182 +vt 0.521161 0.760970 +vt 0.526346 0.785782 +vt 0.529806 0.802339 +vt 0.531065 0.808364 +vt 0.486721 0.562093 +vt 0.494763 0.586939 +vt 0.504445 0.616853 +vt 0.515077 0.649699 +vt 0.525967 0.683346 +vt 0.536426 0.715659 +vt 0.545763 0.744506 +vt 0.553287 0.767752 +vt 0.558308 0.783264 +vt 0.560135 0.788909 +vt 0.492406 0.556472 +vt 0.502723 0.579068 +vt 0.515144 0.606274 +vt 0.528783 0.636147 +vt 0.542754 0.666747 +vt 0.556172 0.696135 +vt 0.568150 0.722370 +vt 0.577802 0.743512 +vt 0.584243 0.757619 +vt 0.586587 0.762753 +vt 0.497446 0.549544 +vt 0.509779 0.569369 +vt 0.524627 0.593236 +vt 0.540932 0.619445 +vt 0.557634 0.646291 +vt 0.573674 0.672074 +vt 0.587993 0.695090 +vt 0.599532 0.713638 +vt 0.607232 0.726015 +vt 0.610034 0.730519 +vt 0.501755 0.541444 +vt 0.515813 0.558027 +vt 0.532738 0.577993 +vt 0.551323 0.599916 +vt 0.570360 0.622374 +vt 0.588643 0.643941 +vt 0.604964 0.663194 +vt 0.618117 0.678710 +vt 0.626893 0.689063 +vt 0.630087 0.692831 +vt 0.505253 0.532305 +vt 0.520710 0.545232 +vt 0.539320 0.560795 +vt 0.559754 0.577885 +vt 0.580687 0.595390 +vt 0.600789 0.612202 +vt 0.618735 0.627210 +vt 0.633197 0.639304 +vt 0.642848 0.647375 +vt 0.646359 0.650312 +vt 0.507854 0.522263 +vt 0.524352 0.531171 +vt 0.544215 0.541896 +vt 0.566025 0.553673 +vt 0.588367 0.565736 +vt 0.609824 0.577322 +vt 0.628978 0.587664 +vt 0.644414 0.595999 +vt 0.654714 0.601560 +vt 0.658462 0.603584 +vt 0.509476 0.511450 +vt 0.526623 0.516031 +vt 0.547267 0.521547 +vt 0.569936 0.527603 +vt 0.593156 0.533808 +vt 0.615457 0.539766 +vt 0.635365 0.545085 +vt 0.651407 0.549371 +vt 0.662113 0.552232 +vt 0.666009 0.553273 +vn -0.929350 -0.369152 -0.003693 +vn -0.917203 -0.369304 -0.149358 +vn -0.947172 -0.283151 -0.150456 +vn -0.959105 -0.282968 -0.000031 +vn -0.986297 -0.053682 -0.155889 +vn -0.998688 -0.050935 -0.000702 +vn -0.932279 0.330302 -0.147465 +vn -0.943907 0.330149 0.000519 +vn -0.600146 0.794153 -0.095370 +vn -0.609210 0.792993 0.000244 +vn -0.033784 0.999390 -0.005036 +vn -0.034150 0.999390 0.000305 +vn 0.385205 0.920774 0.061403 +vn 0.390210 0.920713 0.000244 +vn 0.606494 0.789178 0.096438 +vn 0.614307 0.789026 0.000183 +vn 0.739860 0.662374 0.117588 +vn 0.749352 0.662130 0.000122 +vn 0.828974 0.543504 0.131687 +vn 0.840114 0.542344 0.001038 +vn 0.878262 0.457320 0.139470 +vn 0.889401 0.457106 0.000031 +vn -0.882077 -0.369762 -0.291849 +vn -0.911588 -0.284494 -0.296640 +vn -0.949309 -0.052339 -0.309915 +vn -0.895993 0.333659 -0.292947 +vn -0.578936 0.793146 -0.188940 +vn -0.032502 0.999390 -0.010254 +vn 0.370373 0.920927 0.121158 +vn 0.583270 0.789575 0.190558 +vn 0.711753 0.662801 0.232459 +vn 0.797632 0.543992 0.260475 +vn 0.845119 0.457778 0.275948 +vn -0.825282 -0.370098 -0.426496 +vn -0.853572 -0.283731 -0.436903 +vn -0.888699 -0.051057 -0.455611 +vn -0.839564 0.332499 -0.429548 +vn -0.541734 0.793603 -0.276925 +vn -0.028657 0.999481 -0.014496 +vn 0.346446 0.921079 0.177557 +vn 0.545152 0.790735 0.278359 +vn 0.666128 0.663259 0.341014 +vn 0.746666 0.544389 0.382183 +vn 0.791223 0.458205 0.404950 +vn -0.748619 -0.370373 -0.549852 +vn -0.775323 -0.284982 -0.563585 +vn -0.807184 -0.052431 -0.587939 +vn -0.762291 0.332682 -0.555132 +vn -0.491684 0.793847 -0.357799 +vn -0.027680 0.999420 -0.019745 +vn 0.314249 0.921232 0.229194 +vn 0.494888 0.790948 0.359752 +vn 0.604816 0.662618 0.441664 +vn 0.677786 0.544694 0.493820 +vn 0.718314 0.458480 0.523240 +vn -0.654164 -0.370403 -0.659413 +vn -0.678396 -0.285073 -0.677114 +vn -0.706107 -0.052492 -0.706137 +vn -0.666860 0.332743 -0.666707 +vn -0.430067 0.793939 -0.429731 +vn -0.024232 0.999420 -0.023774 +vn 0.274789 0.921262 0.275155 +vn 0.432905 0.791070 0.432142 +vn 0.528855 0.662709 0.530168 +vn 0.592853 0.544816 0.592975 +vn 0.628346 0.458541 0.628376 +vn -0.543870 -0.370373 -0.752983 +vn -0.564470 -0.283914 -0.775048 +vn -0.587390 -0.051057 -0.807672 +vn -0.554552 0.334178 -0.762078 +vn -0.359264 0.792596 -0.492599 +vn -0.020264 0.999420 -0.027284 +vn 0.228797 0.921201 0.314585 +vn 0.360637 0.790979 0.494217 +vn 0.440168 0.662618 0.605914 +vn 0.493637 0.544725 0.677908 +vn 0.523179 0.458480 0.718375 +vn -0.419874 -0.370067 -0.828669 +vn -0.436781 -0.283761 -0.853603 +vn -0.454909 -0.052370 -0.888974 +vn -0.429731 0.332469 -0.839503 +vn -0.278207 0.792383 -0.542863 +vn -0.014740 0.999481 -0.028504 +vn 0.177068 0.921110 0.346660 +vn 0.279305 0.790735 0.544664 +vn 0.340465 0.662313 0.667379 +vn 0.382031 0.544420 0.746727 +vn 0.406018 0.458754 0.790338 +vn -0.288186 -0.369732 -0.883297 +vn -0.297555 -0.283456 -0.911618 +vn -0.310617 -0.053713 -0.949004 +vn -0.293374 0.330607 -0.896969 +vn -0.189154 0.793268 -0.578692 +vn -0.010102 0.999481 -0.030488 +vn 0.120640 0.920927 0.370525 +vn 0.190588 0.790399 0.582171 +vn 0.231697 0.661885 0.712851 +vn 0.260292 0.543962 0.797693 +vn 0.275826 0.457778 0.845149 +vn -0.142033 -0.369274 -0.918363 +vn -0.150395 -0.283120 -0.947203 +vn -0.156590 -0.052370 -0.986267 +vn -0.147404 0.333445 -0.931150 +vn -0.095157 0.794122 -0.600208 +vn -0.005646 0.999390 -0.033662 +vn 0.060854 0.920743 0.385296 +vn 0.096072 0.789209 0.606525 +vn 0.116642 0.661428 0.740837 +vn 0.132481 0.544359 0.828303 +vn 0.139409 0.457320 0.878292 +vn 0.001648 -0.369884 -0.929044 +vn -0.003174 -0.283914 -0.958831 +vn -0.001709 -0.052156 -0.998627 +vn -0.001617 0.330302 -0.943846 +vn -0.000824 0.791864 -0.610675 +vn -0.000244 0.999359 -0.035493 +vn 0.000122 0.921140 0.389141 +vn 0.000458 0.789727 0.613422 +vn 0.000763 0.662984 0.748589 +vn -0.000092 0.543260 0.839534 +vn -0.001434 0.456526 0.889706 +vn 0.143040 -0.391430 -0.908994 +vn 0.142033 -0.304819 -0.941740 +vn 0.150945 -0.078555 -0.985382 +vn 0.146458 0.306711 -0.940458 +vn 0.097171 0.782525 -0.614978 +vn 0.005463 0.999359 -0.034791 +vn -0.061708 0.919401 0.388409 +vn -0.096408 0.787774 0.608325 +vn -0.117344 0.661641 0.740562 +vn -0.132694 0.542375 0.829585 +vn -0.139439 0.457289 0.878292 +vn 0.275155 -0.432508 -0.858608 +vn 0.282205 -0.351665 -0.892544 +vn 0.301279 -0.135319 -0.943876 +vn 0.297372 0.250191 -0.921384 +vn 0.202185 0.758232 -0.619800 +vn 0.012696 0.999146 -0.038789 +vn -0.123417 0.917417 0.378246 +vn -0.191504 0.785638 0.588275 +vn -0.232582 0.660543 0.713797 +vn -0.261574 0.542344 0.798364 +vn -0.275948 0.457686 0.845180 +vn 0.398968 -0.472152 -0.786035 +vn 0.415082 -0.397595 -0.818293 +vn 0.444929 -0.192145 -0.874691 +vn 0.446608 0.191046 -0.874081 +vn 0.311594 0.731590 -0.606342 +vn 0.021851 0.998840 -0.042268 +vn -0.183599 0.915128 0.358867 +vn -0.284188 0.782098 0.554552 +vn -0.343425 0.658376 0.669729 +vn -0.383587 0.542192 0.747551 +vn -0.404950 0.458022 0.791314 +vn 0.520859 -0.498398 -0.693014 +vn 0.540452 -0.424207 -0.726585 +vn 0.580004 -0.224219 -0.783105 +vn 0.586077 0.158116 -0.794641 +vn 0.414441 0.714133 -0.564074 +vn 0.030915 0.998627 -0.042055 +vn -0.240211 0.913175 0.329203 +vn -0.367901 0.780755 0.505020 +vn -0.441969 0.659169 0.608356 +vn -0.494400 0.542985 0.678732 +vn -0.523301 0.458266 0.718406 +vn 0.631397 -0.502335 -0.590716 +vn 0.651448 -0.429792 -0.625202 +vn 0.698508 -0.231056 -0.677236 +vn 0.706076 0.149663 -0.692099 +vn 0.503464 0.704856 -0.499680 +vn 0.039918 0.998383 -0.039979 +vn -0.289132 0.912839 0.288217 +vn -0.442305 0.780389 0.441908 +vn -0.532395 0.658101 0.532365 +vn -0.594928 0.542100 0.593432 +vn -0.628468 0.458327 0.628407 +vn 0.730033 -0.484939 -0.481491 +vn 0.750755 -0.412915 -0.515580 +vn 0.801843 -0.209693 -0.559496 +vn 0.803339 0.171331 -0.570299 +vn 0.569384 0.711264 -0.412122 +vn 0.045839 0.998352 -0.033784 +vn -0.328623 0.913877 0.238289 +vn -0.504562 0.781518 0.366894 +vn -0.608264 0.658681 0.442854 +vn -0.679800 0.542222 0.493789 +vn -0.718467 0.458235 0.523240 +vn 0.813685 -0.451979 -0.365520 +vn 0.835658 -0.379192 -0.397290 +vn 0.886685 -0.167943 -0.430738 +vn 0.874722 0.213935 -0.434797 +vn 0.608936 0.730003 -0.310251 +vn 0.047426 0.998535 -0.024812 +vn -0.357799 0.915860 0.182043 +vn -0.553178 0.783685 0.282418 +vn -0.669088 0.659719 0.342174 +vn -0.747429 0.543168 0.382427 +vn -0.790429 0.458602 0.406018 +vn 0.877102 -0.413526 -0.244240 +vn 0.901089 -0.338664 -0.270791 +vn 0.948729 -0.119022 -0.292764 +vn 0.919675 0.262734 -0.291726 +vn 0.624622 0.753838 -0.203772 +vn 0.045198 0.998840 -0.015290 +vn -0.376965 0.918119 0.122196 +vn -0.587634 0.786157 0.191260 +vn -0.713462 0.660909 0.232673 +vn -0.798975 0.542405 0.259651 +vn -0.845943 0.457076 0.274606 +vn 0.915830 -0.382366 -0.122440 +vn 0.942137 -0.304971 -0.139134 +vn 0.985504 -0.079470 -0.149754 +vn 0.941923 0.301920 -0.146977 +vn 0.624073 0.774895 -0.099979 +vn 0.040956 0.999115 -0.006897 +vn -0.387341 0.919889 0.060976 +vn -0.607898 0.788141 0.096164 +vn -0.740440 0.661733 0.117405 +vn -0.829920 0.542344 0.130589 +vn -0.878323 0.457289 0.139348 +vn 0.929319 -0.369121 0.008545 +vn 0.957915 -0.286966 0.000793 +vn 0.998383 -0.056673 0.000702 +vn 0.945860 0.324503 -0.000488 +vn 0.615223 0.788324 -0.000732 +vn 0.034303 0.999390 -0.000305 +vn -0.391980 0.919950 -0.000580 +vn -0.614490 0.788903 -0.000122 +vn -0.750206 0.661184 -0.000977 +vn -0.840175 0.542283 -0.001099 +vn -0.889401 0.457106 -0.000031 +vn 0.917203 -0.369304 0.149358 +vn 0.947172 -0.283151 0.150456 +vn 0.986206 -0.050966 0.157384 +vn 0.932279 0.330271 0.147465 +vn 0.600146 0.794153 0.095370 +vn 0.031831 0.999481 0.004883 +vn -0.385205 0.920774 -0.061403 +vn -0.606494 0.789178 -0.096438 +vn -0.739921 0.662313 -0.117466 +vn -0.828608 0.544359 -0.130558 +vn -0.878262 0.457320 -0.139470 +vn 0.882077 -0.369762 0.291849 +vn 0.911588 -0.283425 0.297678 +vn 0.949309 -0.052339 0.309915 +vn 0.896664 0.332133 0.292611 +vn 0.578906 0.793176 0.188940 +vn 0.032502 0.999390 0.010254 +vn -0.370373 0.920927 -0.121158 +vn -0.583270 0.789575 -0.190558 +vn -0.711753 0.662801 -0.232459 +vn -0.797876 0.543077 -0.261574 +vn -0.844966 0.457198 -0.277352 +vn 0.825282 -0.370098 0.426496 +vn 0.853572 -0.283700 0.436903 +vn 0.889218 -0.053774 0.454237 +vn 0.839564 0.332469 0.429548 +vn 0.541734 0.793603 0.276925 +vn 0.028657 0.999481 0.014496 +vn -0.346446 0.921079 -0.177557 +vn -0.545152 0.790735 -0.278359 +vn -0.666128 0.663259 -0.341014 +vn -0.746666 0.544389 -0.382183 +vn -0.791223 0.458205 -0.404950 +vn 0.748619 -0.370373 0.549852 +vn 0.775323 -0.284982 0.563585 +vn 0.807184 -0.052431 0.587939 +vn 0.761559 0.334208 0.555254 +vn 0.491684 0.793847 0.357799 +vn 0.029206 0.999329 0.021027 +vn -0.314280 0.921232 -0.229225 +vn -0.494888 0.790948 -0.359752 +vn -0.604816 0.662618 -0.441664 +vn -0.677786 0.544694 -0.493820 +vn -0.718314 0.458480 -0.523240 +vn 0.654164 -0.370403 0.659413 +vn 0.678396 -0.285073 0.677114 +vn 0.706107 -0.052492 0.706137 +vn 0.667562 0.331217 0.666799 +vn 0.430067 0.793970 0.429701 +vn 0.024262 0.999420 0.023804 +vn -0.275857 0.920530 -0.276528 +vn -0.432936 0.791040 -0.432173 +vn -0.528855 0.662709 -0.530168 +vn -0.592853 0.544816 -0.592975 +vn -0.628346 0.458541 -0.628376 +vn 0.543870 -0.370373 0.752983 +vn 0.564470 -0.283914 0.775048 +vn 0.587390 -0.051057 0.807672 +vn 0.554552 0.334178 0.762078 +vn 0.358074 0.793725 0.491653 +vn 0.020264 0.999420 0.027314 +vn -0.228797 0.921232 -0.314585 +vn -0.360637 0.790979 -0.494217 +vn -0.440168 0.662618 -0.605914 +vn -0.493637 0.544725 -0.677908 +vn -0.523179 0.458480 -0.718375 +vn 0.419874 -0.370067 0.828669 +vn 0.436781 -0.283761 0.853603 +vn 0.454909 -0.052370 0.888974 +vn 0.429731 0.332469 0.839503 +vn 0.278207 0.792383 0.542863 +vn 0.014740 0.999481 0.028504 +vn -0.177068 0.921110 -0.346660 +vn -0.279305 0.790735 -0.544664 +vn -0.340465 0.662313 -0.667379 +vn -0.382031 0.544420 -0.746727 +vn -0.406018 0.458754 -0.790338 +vn 0.288186 -0.369732 0.883297 +vn 0.297555 -0.283456 0.911618 +vn 0.309915 -0.052309 0.949309 +vn 0.292795 0.332163 0.896603 +vn 0.189154 0.793268 0.578692 +vn 0.010102 0.999481 0.030488 +vn -0.120640 0.920927 -0.370525 +vn -0.190588 0.790399 -0.582171 +vn -0.231697 0.661885 -0.712851 +vn -0.260292 0.543962 -0.797693 +vn -0.275826 0.457778 -0.845149 +vn 0.142033 -0.369274 0.918363 +vn 0.150395 -0.283120 0.947203 +vn 0.156590 -0.052309 0.986267 +vn 0.148015 0.331919 0.931608 +vn 0.095767 0.792962 0.601642 +vn 0.005646 0.999390 0.033662 +vn -0.060854 0.920743 -0.385296 +vn -0.096072 0.789209 -0.606525 +vn -0.116642 0.661428 -0.740837 +vn -0.132481 0.544359 -0.828303 +vn -0.139409 0.457320 -0.878292 +vn -0.003693 -0.369152 0.929350 +vn -0.000031 -0.282968 0.959105 +vn -0.000732 -0.050905 0.998688 +vn 0.000061 0.331736 0.943358 +vn 0.000244 0.792962 0.609241 +vn 0.000305 0.999390 0.034150 +vn 0.000244 0.920713 -0.390210 +vn 0.000183 0.789026 -0.614307 +vn 0.000122 0.662130 -0.749352 +vn -0.000946 0.544145 -0.838984 +vn 0.001434 0.456526 -0.889676 +vn -0.149358 -0.369304 0.917203 +vn -0.150456 -0.283151 0.947172 +vn -0.156621 -0.052278 0.986267 +vn -0.147801 0.331858 0.931669 +vn -0.095248 0.792993 0.601703 +vn -0.005036 0.999390 0.033784 +vn 0.061403 0.920774 -0.385205 +vn 0.096438 0.789178 -0.606494 +vn 0.117588 0.662374 -0.739860 +vn 0.131687 0.543504 -0.828974 +vn 0.140904 0.456740 -0.878353 +vn -0.291849 -0.369762 0.882077 +vn -0.297678 -0.283425 0.911588 +vn -0.309915 -0.052339 0.949309 +vn -0.292398 0.330577 0.897305 +vn -0.188665 0.793268 0.578845 +vn -0.010254 0.999390 0.032502 +vn 0.121158 0.920927 -0.370373 +vn 0.190558 0.789544 -0.583300 +vn 0.232459 0.662801 -0.711753 +vn 0.260475 0.543992 -0.797632 +vn 0.275948 0.457778 -0.845119 +vn -0.426496 -0.370098 0.825282 +vn -0.436903 -0.283700 0.853572 +vn -0.454237 -0.053774 0.889218 +vn -0.429548 0.332469 0.839564 +vn -0.276589 0.794702 0.540269 +vn -0.015229 0.999390 0.030457 +vn 0.177557 0.921110 -0.346416 +vn 0.279489 0.789911 -0.545793 +vn 0.341014 0.663228 -0.666158 +vn 0.382183 0.544389 -0.746666 +vn 0.404950 0.458205 -0.791223 +vn -0.549852 -0.370373 0.748619 +vn -0.563585 -0.284982 0.775323 +vn -0.587939 -0.052431 0.807184 +vn -0.555223 0.332621 0.762261 +vn -0.357799 0.793847 0.491684 +vn -0.019745 0.999420 0.027680 +vn 0.229225 0.921232 -0.314280 +vn 0.360973 0.790124 -0.495315 +vn 0.440535 0.663533 -0.604633 +vn 0.493820 0.544694 -0.677786 +vn 0.523240 0.458480 -0.718314 +vn -0.659413 -0.370403 0.654164 +vn -0.677114 -0.285073 0.678396 +vn -0.706137 -0.052492 0.706107 +vn -0.666707 0.332804 0.666860 +vn -0.428938 0.795068 0.428785 +vn -0.025300 0.999329 0.025575 +vn 0.275155 0.921262 -0.274789 +vn 0.432173 0.791040 -0.432936 +vn 0.529008 0.663656 -0.528825 +vn 0.592975 0.544816 -0.592853 +vn 0.628376 0.458541 -0.628346 +vn -0.752983 -0.370373 0.543870 +vn -0.775048 -0.283914 0.564470 +vn -0.807642 -0.051088 0.587390 +vn -0.762078 0.334178 0.554552 +vn -0.492599 0.792627 0.359233 +vn -0.027314 0.999420 0.020264 +vn 0.316080 0.920499 -0.229652 +vn 0.494217 0.790979 -0.360637 +vn 0.605914 0.662618 -0.440168 +vn 0.677908 0.544725 -0.493637 +vn 0.718375 0.458480 -0.523179 +vn -0.828669 -0.370067 0.419874 +vn -0.853603 -0.283731 0.436781 +vn -0.888974 -0.052400 0.454909 +vn -0.839503 0.332438 0.429731 +vn -0.541490 0.793603 0.277352 +vn -0.028504 0.999481 0.014740 +vn 0.346660 0.921110 -0.177068 +vn 0.544664 0.790735 -0.279305 +vn 0.667379 0.662313 -0.340465 +vn 0.746727 0.544420 -0.382031 +vn 0.790338 0.458754 -0.406018 +vn -0.884396 -0.369732 0.284799 +vn -0.911618 -0.283456 0.297555 +vn -0.949004 -0.053713 0.310617 +vn -0.897000 0.330607 0.293374 +vn -0.578692 0.793268 0.189154 +vn -0.034211 0.999329 0.011353 +vn 0.370525 0.920927 -0.120640 +vn 0.582171 0.790399 -0.190588 +vn 0.712851 0.661885 -0.231697 +vn 0.796838 0.544847 -0.261116 +vn 0.845149 0.457778 -0.275826 +vn -0.918363 -0.369274 0.142033 +vn -0.946745 -0.284219 0.151250 +vn -0.986267 -0.052370 0.156590 +vn -0.931150 0.333445 0.147404 +vn -0.600208 0.794122 0.095157 +vn -0.033662 0.999390 0.005646 +vn 0.387036 0.920042 -0.060945 +vn 0.606525 0.789209 -0.096072 +vn 0.739952 0.662313 -0.117435 +vn 0.829005 0.543504 -0.131504 +vn 0.878811 0.456740 -0.138066 +vn 0.893460 0.426099 0.141850 +vn 0.904752 0.425886 0.000000 +vn 0.897702 0.416852 0.142521 +vn 0.909055 0.416639 0.000000 +vn 0.904904 0.401196 0.141881 +vn 0.916166 0.400739 0.000000 +vn 0.914609 0.378002 0.143498 +vn 0.925993 0.377483 0.000000 +vn 0.926633 0.345927 0.147130 +vn 0.938322 0.345744 0.000000 +vn 0.940794 0.304209 0.149388 +vn 0.952635 0.304025 0.000000 +vn 0.956084 0.250649 0.151830 +vn 0.968108 0.250496 0.000000 +vn 0.970916 0.183020 0.154180 +vn 0.983123 0.182928 0.000000 +vn 0.982879 0.099857 0.154759 +vn 0.995056 0.099033 0.000000 +vn 0.987243 -0.027833 0.156743 +vn 0.999603 -0.027833 0.000000 +vn 0.859798 0.426527 0.280679 +vn 0.863887 0.417280 0.282022 +vn 0.870663 0.401379 0.284249 +vn 0.879635 0.377758 0.288949 +vn 0.891415 0.345897 0.292734 +vn 0.905423 0.304544 0.295602 +vn 0.920499 0.251503 0.298990 +vn 0.934812 0.183905 0.303751 +vn 0.945921 0.099216 0.308817 +vn 0.949889 -0.028932 0.311197 +vn 0.804956 0.426954 0.411939 +vn 0.808802 0.417676 0.413923 +vn 0.815180 0.401776 0.417158 +vn 0.823969 0.378460 0.421674 +vn 0.834986 0.346660 0.427320 +vn 0.847804 0.304880 0.433882 +vn 0.861629 0.251198 0.440962 +vn 0.874569 0.182775 0.449110 +vn 0.885250 0.098575 0.454512 +vn 0.889828 -0.027924 0.455397 +vn 0.730827 0.427198 0.532304 +vn 0.734306 0.417951 0.534867 +vn 0.740074 0.402020 0.539079 +vn 0.748070 0.378704 0.544908 +vn 0.758080 0.346904 0.552202 +vn 0.769738 0.305094 0.560686 +vn 0.782311 0.251381 0.569842 +vn 0.794549 0.183599 0.578753 +vn 0.804285 0.099399 0.585833 +vn 0.807978 -0.027924 0.588519 +vn 0.639271 0.427290 0.639271 +vn 0.642323 0.418043 0.642354 +vn 0.647389 0.402112 0.647420 +vn 0.654378 0.378796 0.654408 +vn 0.663137 0.346965 0.663167 +vn 0.673330 0.305155 0.673391 +vn 0.684347 0.251442 0.684378 +vn 0.695059 0.183630 0.695090 +vn 0.703574 0.099429 0.703604 +vn 0.706809 -0.027924 0.706809 +vn 0.532304 0.427198 0.730827 +vn 0.534837 0.417951 0.734306 +vn 0.539048 0.402020 0.740104 +vn 0.544877 0.378704 0.748100 +vn 0.552141 0.346904 0.758110 +vn 0.560656 0.305094 0.769768 +vn 0.569811 0.251381 0.782342 +vn 0.578722 0.183599 0.794580 +vn 0.585833 0.099399 0.804285 +vn 0.588519 -0.027924 0.807978 +vn 0.411908 0.426954 0.804987 +vn 0.415509 0.417829 0.807917 +vn 0.417127 0.401776 0.815180 +vn 0.421644 0.378460 0.823969 +vn 0.427290 0.346660 0.834986 +vn 0.433851 0.304880 0.847804 +vn 0.440931 0.251198 0.861660 +vn 0.447798 0.183447 0.875088 +vn 0.453291 0.099338 0.885800 +vn 0.455397 -0.027894 0.889828 +vn 0.278817 0.426435 0.860439 +vn 0.281991 0.417280 0.863887 +vn 0.282540 0.401135 0.871334 +vn 0.287271 0.378094 0.880062 +vn 0.291086 0.346294 0.891781 +vn 0.295541 0.304544 0.905454 +vn 0.300363 0.250954 0.920194 +vn 0.305063 0.183233 0.934507 +vn 0.308786 0.099216 0.945921 +vn 0.311228 -0.026826 0.949950 +vn 0.141850 0.426099 0.893460 +vn 0.142491 0.416852 0.897702 +vn 0.143620 0.400952 0.904752 +vn 0.145146 0.377697 0.914457 +vn 0.147099 0.345927 0.926633 +vn 0.149327 0.304209 0.940794 +vn 0.153203 0.251228 0.955718 +vn 0.154118 0.183020 0.970916 +vn 0.156011 0.099094 0.982757 +vn 0.156743 -0.027863 0.987213 +vn 0.000000 0.425886 0.904752 +vn 0.001831 0.416791 0.908963 +vn 0.000000 0.400739 0.916166 +vn 0.000000 0.377483 0.925993 +vn 0.000000 0.345744 0.938322 +vn 0.000000 0.304025 0.952635 +vn 0.001434 0.251076 0.967956 +vn -0.001373 0.182257 0.983245 +vn 0.000000 0.099033 0.995056 +vn 0.000000 -0.027833 0.999603 +vn -0.141850 0.426099 0.893460 +vn -0.142521 0.416852 0.897702 +vn -0.143651 0.400952 0.904752 +vn -0.145207 0.377667 0.914457 +vn -0.147130 0.345927 0.926633 +vn -0.149388 0.304209 0.940794 +vn -0.151830 0.250649 0.956084 +vn -0.155553 0.182348 0.970824 +vn -0.154759 0.099857 0.982879 +vn -0.156743 -0.027833 0.987243 +vn -0.280679 0.426527 0.859798 +vn -0.282022 0.417280 0.863887 +vn -0.284249 0.401379 0.870663 +vn -0.287271 0.378094 0.880032 +vn -0.291147 0.346294 0.891781 +vn -0.295602 0.304544 0.905423 +vn -0.298990 0.251503 0.920499 +vn -0.305094 0.183233 0.934507 +vn -0.308817 0.099216 0.945921 +vn -0.311197 -0.028932 0.949889 +vn -0.411939 0.426954 0.804956 +vn -0.413923 0.417676 0.808802 +vn -0.417158 0.401776 0.815180 +vn -0.421674 0.378460 0.823969 +vn -0.428816 0.346263 0.834376 +vn -0.433882 0.304880 0.847804 +vn -0.440962 0.251198 0.861629 +vn -0.449110 0.182775 0.874569 +vn -0.453322 0.099338 0.885769 +vn -0.455397 -0.027924 0.889828 +vn -0.532304 0.427198 0.730827 +vn -0.534867 0.417951 0.734306 +vn -0.539079 0.402020 0.740074 +vn -0.544877 0.378704 0.748070 +vn -0.552202 0.346904 0.758080 +vn -0.560686 0.305094 0.769738 +vn -0.569842 0.251381 0.782311 +vn -0.577563 0.184271 0.795251 +vn -0.585833 0.099399 0.804285 +vn -0.588519 -0.027924 0.807978 +vn -0.639271 0.427290 0.639271 +vn -0.642354 0.418043 0.642323 +vn -0.647420 0.402112 0.647389 +vn -0.654408 0.378796 0.654378 +vn -0.663167 0.346965 0.663137 +vn -0.673360 0.305155 0.673360 +vn -0.684378 0.251442 0.684347 +vn -0.695090 0.183630 0.695059 +vn -0.704550 0.098666 0.702719 +vn -0.706809 -0.027924 0.706809 +vn -0.730827 0.427198 0.532304 +vn -0.734306 0.417951 0.534837 +vn -0.740104 0.402020 0.539048 +vn -0.748100 0.378704 0.544877 +vn -0.758110 0.346904 0.552141 +vn -0.769768 0.305094 0.560656 +vn -0.782342 0.251381 0.569811 +vn -0.794580 0.183599 0.578722 +vn -0.803461 0.100162 0.586810 +vn -0.807978 -0.027924 0.588519 +vn -0.804987 0.426923 0.411908 +vn -0.808832 0.417676 0.413892 +vn -0.815180 0.401776 0.417127 +vn -0.823969 0.378460 0.421644 +vn -0.834986 0.346660 0.427290 +vn -0.847804 0.304880 0.433851 +vn -0.861660 0.251198 0.440931 +vn -0.875088 0.183447 0.447798 +vn -0.885800 0.099338 0.453291 +vn -0.890286 -0.028962 0.454451 +vn -0.860439 0.426435 0.278817 +vn -0.863887 0.417280 0.281991 +vn -0.870663 0.401379 0.284219 +vn -0.880062 0.378094 0.287271 +vn -0.891781 0.346294 0.291086 +vn -0.905454 0.304544 0.295541 +vn -0.920194 0.250954 0.300363 +vn -0.934507 0.183233 0.305063 +vn -0.945921 0.099216 0.308786 +vn -0.949950 -0.026826 0.311228 +vn -0.893460 0.426099 0.141850 +vn -0.897702 0.416852 0.142491 +vn -0.904752 0.400952 0.143620 +vn -0.914457 0.377697 0.145146 +vn -0.926633 0.345927 0.147099 +vn -0.940794 0.304209 0.149327 +vn -0.956084 0.250649 0.151769 +vn -0.970916 0.183020 0.154118 +vn -0.982757 0.099094 0.156011 +vn -0.987213 -0.027863 0.156743 +vn -0.904752 0.425886 0.000000 +vn -0.909055 0.416639 0.000000 +vn -0.916166 0.400739 0.000000 +vn -0.925993 0.377483 0.000000 +vn -0.938322 0.345714 0.000000 +vn -0.952635 0.304025 0.000000 +vn -0.968108 0.250496 0.000000 +vn -0.983123 0.182928 0.000000 +vn -0.995056 0.099033 0.000000 +vn -0.972533 0.232734 -0.000641 +vn -0.893460 0.426099 -0.141850 +vn -0.897916 0.417005 -0.140660 +vn -0.904904 0.401196 -0.141881 +vn -0.914457 0.377667 -0.145207 +vn -0.926756 0.346324 -0.145482 +vn -0.940794 0.304209 -0.149388 +vn -0.956175 0.251198 -0.150334 +vn -0.971007 0.183691 -0.152776 +vn -0.982757 0.099094 -0.156041 +vn -0.987243 -0.027833 -0.156743 +vn -0.859798 0.426527 -0.280679 +vn -0.863887 0.417280 -0.282022 +vn -0.870205 0.401135 -0.285958 +vn -0.879635 0.377758 -0.288949 +vn -0.891781 0.346294 -0.291147 +vn -0.905118 0.304056 -0.297128 +vn -0.920499 0.251503 -0.298990 +vn -0.934507 0.183233 -0.305094 +vn -0.945585 0.098453 -0.310068 +vn -0.950224 -0.027894 -0.310221 +vn -0.804956 0.426954 -0.411939 +vn -0.808802 0.417676 -0.413923 +vn -0.815180 0.401776 -0.417158 +vn -0.823969 0.378460 -0.421674 +vn -0.834986 0.346660 -0.427320 +vn -0.847804 0.304880 -0.433882 +vn -0.861629 0.251198 -0.440962 +vn -0.874569 0.182775 -0.449110 +vn -0.885250 0.098575 -0.454512 +vn -0.889828 -0.027924 -0.455397 +vn -0.730827 0.427198 -0.532304 +vn -0.734306 0.417951 -0.534867 +vn -0.740074 0.402020 -0.539079 +vn -0.748070 0.378704 -0.544908 +vn -0.758080 0.346904 -0.552202 +vn -0.770531 0.305551 -0.559343 +vn -0.782311 0.251381 -0.569842 +vn -0.795251 0.184271 -0.577563 +vn -0.804285 0.099399 -0.585833 +vn -0.807978 -0.027924 -0.588519 +vn -0.639271 0.427290 -0.639271 +vn -0.642323 0.418043 -0.642354 +vn -0.647389 0.402112 -0.647420 +vn -0.654378 0.378796 -0.654408 +vn -0.663137 0.346965 -0.663167 +vn -0.673330 0.305155 -0.673391 +vn -0.683432 0.250862 -0.685507 +vn -0.695059 0.183630 -0.695090 +vn -0.702719 0.098666 -0.704550 +vn -0.706809 -0.027924 -0.706809 +vn -0.532304 0.427198 -0.730827 +vn -0.534837 0.417951 -0.734306 +vn -0.539048 0.402020 -0.740104 +vn -0.546159 0.379040 -0.746971 +vn -0.552141 0.346904 -0.758110 +vn -0.560656 0.305094 -0.769768 +vn -0.569811 0.251381 -0.782342 +vn -0.578722 0.183599 -0.794580 +vn -0.585833 0.099399 -0.804285 +vn -0.588519 -0.027924 -0.807978 +vn -0.411908 0.426954 -0.804987 +vn -0.413892 0.417676 -0.808832 +vn -0.417127 0.401776 -0.815180 +vn -0.421644 0.378460 -0.823969 +vn -0.425916 0.346233 -0.835871 +vn -0.433851 0.304880 -0.847804 +vn -0.440931 0.251198 -0.861660 +vn -0.447798 0.183447 -0.875088 +vn -0.453291 0.099338 -0.885800 +vn -0.455397 -0.027894 -0.889828 +vn -0.278817 0.426435 -0.860439 +vn -0.281991 0.417280 -0.863887 +vn -0.284219 0.401379 -0.870663 +vn -0.287271 0.378094 -0.880062 +vn -0.291086 0.346294 -0.891781 +vn -0.295541 0.304544 -0.905454 +vn -0.300363 0.250954 -0.920194 +vn -0.305063 0.183233 -0.934507 +vn -0.308786 0.099216 -0.945921 +vn -0.311228 -0.026826 -0.949950 +vn -0.141850 0.426099 -0.893460 +vn -0.142491 0.416852 -0.897702 +vn -0.143620 0.400952 -0.904752 +vn -0.145146 0.377697 -0.914457 +vn -0.147099 0.345927 -0.926633 +vn -0.149327 0.304209 -0.940794 +vn -0.153203 0.251228 -0.955718 +vn -0.154118 0.183020 -0.970916 +vn -0.156011 0.099094 -0.982757 +vn -0.156743 -0.027863 -0.987213 +vn 0.000000 0.425886 -0.904752 +vn 0.000000 0.416639 -0.909055 +vn 0.000000 0.400739 -0.916166 +vn 0.000000 0.377483 -0.925993 +vn 0.000000 0.345744 -0.938322 +vn 0.000000 0.304025 -0.952635 +vn -0.001434 0.251076 -0.967956 +vn 0.001373 0.182257 -0.983245 +vn 0.000000 0.099033 -0.995056 +vn 0.000000 -0.027833 -0.999603 +vn 0.141850 0.426099 -0.893460 +vn 0.142521 0.416852 -0.897702 +vn 0.141881 0.401196 -0.904904 +vn 0.145207 0.377667 -0.914457 +vn 0.147130 0.345927 -0.926633 +vn 0.149388 0.304209 -0.940794 +vn 0.151830 0.250649 -0.956084 +vn 0.155553 0.182348 -0.970824 +vn 0.154759 0.099857 -0.982879 +vn 0.156743 -0.027833 -0.987243 +vn 0.280679 0.426527 -0.859798 +vn 0.282022 0.417280 -0.863887 +vn 0.284219 0.401379 -0.870663 +vn 0.287271 0.378094 -0.880032 +vn 0.291147 0.346294 -0.891781 +vn 0.295602 0.304544 -0.905423 +vn 0.298990 0.251503 -0.920499 +vn 0.305094 0.183233 -0.934507 +vn 0.308817 0.099216 -0.945921 +vn 0.311197 -0.028932 -0.949889 +vn 0.411939 0.426954 -0.804956 +vn 0.413923 0.417676 -0.808802 +vn 0.417158 0.401776 -0.815180 +vn 0.421674 0.378460 -0.823969 +vn 0.428816 0.346263 -0.834376 +vn 0.433882 0.304880 -0.847804 +vn 0.440962 0.251198 -0.861629 +vn 0.449110 0.182775 -0.874569 +vn 0.453322 0.099338 -0.885769 +vn 0.455397 -0.027924 -0.889828 +vn 0.532304 0.427198 -0.730827 +vn 0.534867 0.417951 -0.734306 +vn 0.539079 0.402020 -0.740074 +vn 0.544908 0.378704 -0.748070 +vn 0.552202 0.346904 -0.758080 +vn 0.560686 0.305063 -0.769738 +vn 0.569842 0.251381 -0.782311 +vn 0.577563 0.184271 -0.795251 +vn 0.585833 0.099399 -0.804285 +vn 0.588519 -0.027924 -0.807978 +vn 0.639271 0.427290 -0.639271 +vn 0.642354 0.418043 -0.642323 +vn 0.647420 0.402112 -0.647389 +vn 0.654408 0.378796 -0.654378 +vn 0.663167 0.346965 -0.663137 +vn 0.673391 0.305155 -0.673330 +vn 0.684378 0.251442 -0.684347 +vn 0.695090 0.183630 -0.695059 +vn 0.704550 0.098666 -0.702719 +vn 0.706809 -0.027924 -0.706809 +vn 0.730827 0.427198 -0.532304 +vn 0.734306 0.417951 -0.534837 +vn 0.740104 0.402020 -0.539048 +vn 0.748100 0.378704 -0.544877 +vn 0.758110 0.346904 -0.552141 +vn 0.769768 0.305094 -0.560656 +vn 0.782342 0.251381 -0.569811 +vn 0.794580 0.183599 -0.578722 +vn 0.804285 0.099399 -0.585833 +vn 0.807978 -0.027924 -0.588519 +vn 0.804987 0.426954 -0.411908 +vn 0.808832 0.417676 -0.413892 +vn 0.815180 0.401776 -0.417127 +vn 0.823969 0.378460 -0.421644 +vn 0.834986 0.346660 -0.427290 +vn 0.847804 0.304880 -0.433821 +vn 0.861660 0.251198 -0.440931 +vn 0.875088 0.183447 -0.447798 +vn 0.885800 0.099338 -0.453291 +vn 0.889828 -0.027894 -0.455397 +vn 0.860439 0.426435 -0.278817 +vn 0.863887 0.417280 -0.281991 +vn 0.870663 0.401379 -0.284219 +vn 0.880062 0.378094 -0.287271 +vn 0.891781 0.346294 -0.291086 +vn 0.905454 0.304544 -0.295541 +vn 0.920194 0.250954 -0.300363 +vn 0.934507 0.183233 -0.305063 +vn 0.945921 0.099216 -0.308786 +vn 0.950224 -0.027894 -0.310221 +vn 0.893460 0.426099 -0.141850 +vn 0.897702 0.416852 -0.142491 +vn 0.904752 0.400952 -0.143620 +vn 0.914457 0.377697 -0.145146 +vn 0.926633 0.345927 -0.147099 +vn 0.940794 0.304209 -0.149327 +vn 0.956084 0.250649 -0.151769 +vn 0.970916 0.183020 -0.154118 +vn 0.982757 0.099094 -0.156011 +vn 0.987213 -0.027863 -0.156743 +vn 0.965361 -0.211097 0.153233 +vn 0.977722 -0.209784 -0.000885 +vn 0.906125 -0.397412 0.144749 +vn 0.918149 -0.396161 -0.000061 +vn 0.831202 -0.540056 0.131870 +vn 0.841762 -0.539811 -0.000092 +vn 0.756584 -0.642750 0.119999 +vn 0.766900 -0.641743 -0.001099 +vn 0.693899 -0.711570 0.110141 +vn 0.702780 -0.711356 -0.000092 +vn 0.649678 -0.753166 0.103030 +vn 0.658040 -0.752953 -0.000061 +vn 0.631428 -0.768914 0.100223 +vn 0.639546 -0.768731 0.000000 +vn 0.658162 -0.745537 0.104556 +vn 0.666646 -0.745323 0.000092 +vn 0.793695 -0.595172 0.125492 +vn 0.804590 -0.593799 0.000122 +vn 0.939268 -0.308969 0.149205 +vn 0.951109 -0.308817 0.000061 +vn 0.929136 -0.211341 0.303293 +vn 0.872585 -0.396771 0.284768 +vn 0.799799 -0.540513 0.260994 +vn 0.727897 -0.643208 0.237495 +vn 0.667501 -0.712027 0.217780 +vn 0.624043 -0.753960 0.205145 +vn 0.607318 -0.769311 0.198218 +vn 0.633045 -0.745964 0.206732 +vn 0.764367 -0.594440 0.249672 +vn 0.904202 -0.307901 0.295938 +vn 0.870052 -0.211554 0.445204 +vn 0.816980 -0.397168 0.418012 +vn 0.748741 -0.540971 0.383038 +vn 0.681326 -0.643635 0.348521 +vn 0.624714 -0.712424 0.319559 +vn 0.584826 -0.753929 0.299173 +vn 0.568316 -0.769677 0.290780 +vn 0.592425 -0.746361 0.303262 +vn 0.715476 -0.594867 0.366283 +vn 0.846400 -0.309580 0.433241 +vn 0.790002 -0.211707 0.575365 +vn 0.741752 -0.397412 0.540178 +vn 0.679708 -0.541246 0.494949 +vn 0.618458 -0.643941 0.450331 +vn 0.567034 -0.712699 0.412885 +vn 0.530808 -0.754204 0.386517 +vn 0.515793 -0.769921 0.375683 +vn 0.537675 -0.746605 0.391736 +vn 0.649434 -0.595203 0.473190 +vn 0.768548 -0.311136 0.559008 +vn 0.691092 -0.211768 0.691031 +vn 0.648885 -0.397504 0.648762 +vn 0.594592 -0.541368 0.594440 +vn 0.540971 -0.644032 0.540849 +vn 0.495987 -0.712790 0.495865 +vn 0.464278 -0.754295 0.464156 +vn 0.451155 -0.770012 0.451125 +vn 0.470260 -0.746696 0.470382 +vn 0.568041 -0.595294 0.568255 +vn 0.672384 -0.311258 0.671529 +vn 0.575427 -0.211707 0.789941 +vn 0.540300 -0.397443 0.741661 +vn 0.495102 -0.541246 0.679617 +vn 0.450484 -0.643941 0.618336 +vn 0.413038 -0.712699 0.566912 +vn 0.386639 -0.754204 0.530717 +vn 0.375683 -0.769921 0.515793 +vn 0.391552 -0.746605 0.537767 +vn 0.472976 -0.595172 0.649617 +vn 0.559709 -0.309793 0.768548 +vn 0.445296 -0.211554 0.870022 +vn 0.418134 -0.397168 0.816919 +vn 0.384320 -0.540025 0.748741 +vn 0.348704 -0.643635 0.681234 +vn 0.319742 -0.712424 0.624622 +vn 0.299295 -0.753929 0.584765 +vn 0.290811 -0.769677 0.568316 +vn 0.303079 -0.746361 0.592517 +vn 0.366039 -0.594867 0.715598 +vn 0.433119 -0.309580 0.846492 +vn 0.303385 -0.211341 0.929106 +vn 0.284921 -0.396771 0.872555 +vn 0.261177 -0.540513 0.799738 +vn 0.236427 -0.643941 0.727592 +vn 0.217963 -0.712027 0.667440 +vn 0.204047 -0.753563 0.624866 +vn 0.198248 -0.769311 0.607318 +vn 0.207556 -0.746513 0.632130 +vn 0.249397 -0.594470 0.764458 +vn 0.295053 -0.309275 0.904019 +vn 0.152379 -0.212256 0.965239 +vn 0.144017 -0.396374 0.906705 +vn 0.132054 -0.540056 0.831172 +vn 0.120212 -0.642750 0.756554 +vn 0.111484 -0.710990 0.694266 +vn 0.103183 -0.753166 0.649648 +vn 0.100223 -0.768914 0.631428 +vn 0.104373 -0.745537 0.658193 +vn 0.125614 -0.592730 0.795495 +vn 0.149052 -0.308969 0.939299 +vn 0.000885 -0.209784 0.977722 +vn 0.000061 -0.396161 0.918149 +vn 0.000092 -0.539811 0.841762 +vn 0.000092 -0.642506 0.766259 +vn 0.000092 -0.711356 0.702780 +vn -0.001373 -0.753349 0.657582 +vn 0.000000 -0.768731 0.639546 +vn -0.000092 -0.745323 0.666646 +vn -0.000122 -0.593799 0.804590 +vn -0.000061 -0.308817 0.951109 +vn -0.153233 -0.211097 0.965361 +vn -0.144749 -0.397412 0.906125 +vn -0.131870 -0.540056 0.831202 +vn -0.119999 -0.642750 0.756584 +vn -0.110050 -0.711600 0.693899 +vn -0.103030 -0.753166 0.649678 +vn -0.098300 -0.768853 0.631764 +vn -0.104556 -0.745537 0.658162 +vn -0.126286 -0.594012 0.794458 +vn -0.149205 -0.308969 0.939268 +vn -0.303293 -0.211341 0.929136 +vn -0.284768 -0.396771 0.872585 +vn -0.260994 -0.540513 0.799799 +vn -0.237495 -0.643208 0.727897 +vn -0.217780 -0.712027 0.667501 +vn -0.203894 -0.753563 0.624928 +vn -0.198218 -0.769311 0.607318 +vn -0.208045 -0.745384 0.633290 +vn -0.249672 -0.594440 0.764367 +vn -0.295206 -0.309305 0.903958 +vn -0.444563 -0.210364 0.870663 +vn -0.418012 -0.397168 0.816980 +vn -0.383038 -0.540971 0.748741 +vn -0.348521 -0.643635 0.681326 +vn -0.319559 -0.712424 0.624714 +vn -0.299173 -0.753929 0.584826 +vn -0.290780 -0.769677 0.568316 +vn -0.303262 -0.746361 0.592425 +vn -0.366283 -0.594867 0.715476 +vn -0.433241 -0.309580 0.846400 +vn -0.575365 -0.211707 0.790002 +vn -0.540696 -0.398480 0.740837 +vn -0.494949 -0.541246 0.679708 +vn -0.450331 -0.643941 0.618458 +vn -0.412885 -0.712699 0.567034 +vn -0.386517 -0.754204 0.530808 +vn -0.375683 -0.769921 0.515793 +vn -0.391736 -0.746605 0.537675 +vn -0.473190 -0.595203 0.649434 +vn -0.559008 -0.311136 0.768548 +vn -0.691031 -0.211768 0.691092 +vn -0.648762 -0.397504 0.648885 +vn -0.594440 -0.541368 0.594592 +vn -0.540849 -0.644032 0.540971 +vn -0.495865 -0.712790 0.495987 +vn -0.464156 -0.754295 0.464278 +vn -0.451125 -0.770012 0.451155 +vn -0.470382 -0.746696 0.470260 +vn -0.568255 -0.595294 0.568041 +vn -0.671529 -0.311258 0.672384 +vn -0.789941 -0.211707 0.575427 +vn -0.741661 -0.397443 0.540300 +vn -0.679617 -0.541246 0.495102 +vn -0.618336 -0.643941 0.450484 +vn -0.566912 -0.712699 0.413038 +vn -0.530717 -0.754204 0.386639 +vn -0.515793 -0.769921 0.375683 +vn -0.537767 -0.746605 0.391552 +vn -0.649617 -0.595172 0.472976 +vn -0.768548 -0.309793 0.559709 +vn -0.870022 -0.211554 0.445296 +vn -0.816919 -0.397168 0.418134 +vn -0.748650 -0.540971 0.383221 +vn -0.681234 -0.643635 0.348704 +vn -0.624622 -0.712424 0.319742 +vn -0.584765 -0.753929 0.299295 +vn -0.567461 -0.769646 0.292581 +vn -0.592517 -0.746361 0.303079 +vn -0.715598 -0.594867 0.366039 +vn -0.846492 -0.309580 0.433119 +vn -0.929106 -0.211341 0.303385 +vn -0.872555 -0.396771 0.284921 +vn -0.800012 -0.539598 0.262246 +vn -0.727836 -0.643208 0.237678 +vn -0.667440 -0.712027 0.217963 +vn -0.624866 -0.753563 0.204047 +vn -0.607318 -0.769311 0.198248 +vn -0.634083 -0.745384 0.205664 +vn -0.764458 -0.594470 0.249397 +vn -0.904019 -0.309305 0.295053 +vn -0.965239 -0.212256 0.152379 +vn -0.906705 -0.396374 0.144017 +vn -0.831172 -0.540056 0.132054 +vn -0.756127 -0.643452 0.118992 +vn -0.693869 -0.711600 0.110233 +vn -0.649648 -0.753166 0.103183 +vn -0.631428 -0.768914 0.100223 +vn -0.658193 -0.745537 0.104373 +vn -0.794519 -0.594012 0.125980 +vn -0.939299 -0.308969 0.149052 +vn -0.977477 -0.210974 0.000031 +vn -0.918149 -0.396161 0.000061 +vn -0.841762 -0.539811 0.000092 +vn -0.766259 -0.642506 0.000092 +vn -0.702780 -0.711356 0.000092 +vn -0.658040 -0.752953 0.000061 +vn -0.639546 -0.768731 0.000000 +vn -0.666646 -0.745323 -0.000092 +vn -0.804590 -0.593799 -0.000122 +vn -0.951109 -0.308817 -0.000061 +vn -0.965361 -0.211097 -0.153233 +vn -0.906735 -0.396374 -0.143895 +vn -0.831202 -0.540056 -0.131870 +vn -0.756584 -0.642750 -0.119999 +vn -0.693899 -0.711600 -0.110050 +vn -0.649678 -0.753166 -0.103030 +vn -0.631397 -0.768914 -0.100223 +vn -0.658162 -0.745537 -0.104556 +vn -0.794458 -0.594012 -0.126286 +vn -0.939268 -0.308969 -0.149205 +vn -0.929136 -0.211341 -0.303293 +vn -0.873318 -0.395703 -0.284097 +vn -0.800653 -0.539567 -0.260262 +vn -0.727897 -0.643208 -0.237495 +vn -0.667501 -0.712027 -0.217780 +vn -0.624928 -0.753563 -0.203894 +vn -0.607318 -0.769311 -0.198218 +vn -0.633045 -0.745964 -0.206732 +vn -0.764367 -0.594440 -0.249672 +vn -0.903958 -0.309305 -0.295206 +vn -0.870052 -0.211554 -0.445204 +vn -0.816980 -0.397168 -0.418012 +vn -0.747734 -0.541856 -0.383679 +vn -0.680258 -0.644368 -0.349254 +vn -0.624714 -0.712424 -0.319559 +vn -0.584826 -0.753929 -0.299173 +vn -0.568316 -0.769677 -0.290811 +vn -0.592425 -0.746361 -0.303262 +vn -0.715476 -0.594867 -0.366283 +vn -0.846400 -0.309580 -0.433241 +vn -0.790002 -0.211707 -0.575365 +vn -0.741752 -0.397412 -0.540178 +vn -0.679708 -0.541246 -0.494949 +vn -0.618458 -0.643941 -0.450331 +vn -0.568224 -0.712088 -0.412305 +vn -0.530808 -0.754204 -0.386517 +vn -0.515793 -0.769921 -0.375683 +vn -0.537675 -0.746605 -0.391736 +vn -0.649434 -0.595203 -0.473190 +vn -0.768487 -0.309793 -0.559832 +vn -0.691092 -0.211768 -0.691031 +vn -0.648885 -0.397504 -0.648762 +vn -0.594592 -0.541368 -0.594440 +vn -0.540971 -0.644032 -0.540849 +vn -0.495987 -0.712790 -0.495865 +vn -0.462935 -0.754692 -0.464888 +vn -0.451155 -0.770012 -0.451125 +vn -0.470260 -0.746696 -0.470382 +vn -0.568041 -0.595294 -0.568255 +vn -0.672201 -0.309915 -0.672323 +vn -0.575427 -0.211707 -0.789941 +vn -0.540300 -0.397443 -0.741661 +vn -0.495102 -0.541246 -0.679617 +vn -0.450484 -0.643941 -0.618336 +vn -0.413038 -0.712699 -0.566912 +vn -0.386639 -0.754204 -0.530717 +vn -0.375683 -0.769921 -0.515793 +vn -0.391552 -0.746605 -0.537767 +vn -0.472976 -0.595172 -0.649617 +vn -0.559709 -0.309793 -0.768548 +vn -0.445296 -0.211554 -0.870022 +vn -0.418134 -0.397168 -0.816919 +vn -0.383221 -0.540971 -0.748650 +vn -0.348704 -0.643635 -0.681234 +vn -0.319742 -0.712424 -0.624622 +vn -0.299295 -0.753929 -0.584765 +vn -0.292550 -0.769646 -0.567461 +vn -0.303903 -0.746910 -0.591388 +vn -0.366039 -0.594867 -0.715598 +vn -0.433119 -0.309580 -0.846461 +vn -0.303385 -0.211341 -0.929106 +vn -0.284921 -0.396771 -0.872555 +vn -0.261177 -0.540513 -0.799738 +vn -0.238868 -0.642445 -0.728141 +vn -0.217963 -0.712027 -0.667440 +vn -0.204047 -0.753563 -0.624866 +vn -0.198248 -0.769311 -0.607318 +vn -0.205664 -0.745384 -0.634083 +vn -0.249184 -0.593188 -0.765496 +vn -0.295053 -0.309275 -0.904019 +vn -0.152379 -0.212256 -0.965239 +vn -0.144017 -0.396374 -0.906705 +vn -0.133061 -0.539109 -0.831629 +vn -0.120212 -0.642750 -0.756554 +vn -0.108890 -0.712149 -0.693503 +vn -0.103183 -0.753166 -0.649648 +vn -0.100223 -0.768914 -0.631428 +vn -0.105502 -0.746086 -0.657399 +vn -0.125980 -0.594012 -0.794519 +vn -0.149052 -0.308969 -0.939299 +vn -0.000885 -0.209784 -0.977722 +vn -0.000061 -0.396161 -0.918149 +vn -0.000092 -0.539811 -0.841762 +vn 0.000000 -0.642476 -0.766289 +vn -0.000092 -0.711356 -0.702780 +vn -0.001434 -0.752556 -0.658498 +vn 0.000000 -0.768731 -0.639546 +vn 0.000092 -0.745323 -0.666646 +vn 0.000641 -0.592547 -0.805506 +vn 0.000061 -0.308817 -0.951109 +vn 0.153233 -0.211097 -0.965361 +vn 0.144749 -0.397412 -0.906125 +vn 0.131870 -0.540056 -0.831202 +vn 0.119999 -0.642750 -0.756584 +vn 0.111179 -0.712180 -0.693106 +vn 0.103030 -0.753166 -0.649678 +vn 0.102145 -0.768975 -0.631062 +vn 0.104556 -0.745537 -0.658162 +vn 0.126286 -0.594012 -0.794458 +vn 0.148442 -0.310312 -0.938932 +vn 0.303293 -0.211341 -0.929136 +vn 0.284768 -0.396771 -0.872585 +vn 0.260994 -0.540513 -0.799799 +vn 0.237495 -0.643208 -0.727897 +vn 0.216865 -0.711417 -0.668447 +vn 0.203894 -0.753563 -0.624928 +vn 0.198218 -0.769311 -0.607318 +vn 0.206732 -0.745964 -0.633045 +vn 0.249672 -0.594440 -0.764367 +vn 0.295206 -0.309305 -0.903958 +vn 0.444563 -0.210364 -0.870663 +vn 0.418012 -0.397168 -0.816980 +vn 0.383038 -0.540971 -0.748741 +vn 0.348521 -0.643635 -0.681326 +vn 0.319559 -0.712424 -0.624714 +vn 0.300272 -0.754326 -0.583758 +vn 0.290780 -0.769677 -0.568316 +vn 0.303262 -0.746361 -0.592425 +vn 0.366283 -0.594867 -0.715476 +vn 0.433241 -0.309580 -0.846400 +vn 0.575365 -0.211707 -0.790002 +vn 0.540696 -0.398480 -0.740837 +vn 0.494949 -0.541246 -0.679708 +vn 0.450331 -0.643941 -0.618458 +vn 0.412885 -0.712699 -0.567034 +vn 0.386517 -0.754204 -0.530808 +vn 0.375683 -0.769921 -0.515793 +vn 0.390362 -0.747154 -0.537889 +vn 0.473190 -0.595203 -0.649434 +vn 0.559832 -0.309793 -0.768487 +vn 0.691031 -0.211768 -0.691092 +vn 0.648762 -0.397504 -0.648885 +vn 0.594440 -0.541368 -0.594592 +vn 0.540849 -0.644032 -0.540971 +vn 0.495865 -0.712790 -0.495987 +vn 0.464156 -0.754295 -0.464278 +vn 0.451125 -0.770012 -0.451155 +vn 0.470382 -0.746696 -0.470260 +vn 0.569262 -0.594043 -0.568346 +vn 0.672323 -0.309915 -0.672201 +vn 0.789941 -0.211707 -0.575427 +vn 0.741661 -0.397443 -0.540300 +vn 0.679617 -0.541246 -0.495102 +vn 0.618336 -0.643941 -0.450484 +vn 0.566912 -0.712699 -0.413038 +vn 0.530717 -0.754204 -0.386639 +vn 0.515793 -0.769921 -0.375683 +vn 0.537797 -0.746605 -0.391552 +vn 0.648518 -0.596362 -0.472976 +vn 0.768548 -0.309793 -0.559709 +vn 0.870022 -0.211554 -0.445296 +vn 0.816919 -0.397168 -0.418134 +vn 0.748650 -0.540971 -0.383221 +vn 0.681234 -0.643635 -0.348704 +vn 0.624622 -0.712424 -0.319742 +vn 0.584765 -0.753929 -0.299295 +vn 0.568316 -0.769677 -0.290811 +vn 0.592517 -0.746361 -0.303079 +vn 0.715598 -0.594867 -0.366039 +vn 0.847163 -0.308176 -0.432752 +vn 0.929106 -0.211341 -0.303385 +vn 0.872555 -0.396771 -0.284921 +vn 0.799738 -0.540513 -0.261177 +vn 0.727836 -0.643208 -0.237678 +vn 0.667440 -0.712027 -0.217963 +vn 0.624866 -0.753563 -0.204047 +vn 0.607318 -0.769311 -0.198248 +vn 0.633106 -0.745964 -0.206549 +vn 0.764458 -0.594470 -0.249397 +vn 0.904019 -0.309305 -0.295053 +vn 0.965361 -0.211097 -0.153325 +vn 0.906705 -0.396374 -0.144017 +vn 0.831172 -0.540056 -0.132054 +vn 0.756554 -0.642750 -0.120212 +vn 0.693869 -0.711600 -0.110233 +vn 0.649648 -0.753166 -0.103183 +vn 0.631397 -0.768914 -0.100223 +vn 0.658193 -0.745537 -0.104373 +vn 0.794519 -0.594012 -0.125980 +vn 0.939299 -0.308969 -0.149052 +vn 0.916135 -0.373486 0.145360 +vn 0.927671 -0.373333 -0.000092 +vn 0.676534 -0.728599 0.106845 +vn 0.683798 -0.729667 -0.000183 +vn 0.408185 -0.910581 0.064516 +vn 0.412030 -0.911161 0.000305 +vn 0.240150 -0.969970 0.037843 +vn 0.243233 -0.969939 -0.000214 +vn 0.145329 -0.989105 0.022828 +vn 0.147191 -0.989105 -0.000214 +vn 0.089175 -0.995911 0.013916 +vn 0.090304 -0.995911 -0.000214 +vn 0.053560 -0.998505 0.008271 +vn 0.054231 -0.998505 -0.000183 +vn 0.029664 -0.999542 0.004486 +vn 0.030030 -0.999542 -0.000183 +vn 0.014405 -0.999878 0.001953 +vn 0.014557 -0.999878 -0.000305 +vn 0.000000 -1.000000 0.000000 +vn 0.881710 -0.373852 0.287729 +vn 0.650868 -0.728965 0.211890 +vn 0.391034 -0.911405 0.127964 +vn 0.232551 -0.969665 0.075228 +vn 0.139744 -0.989135 0.045351 +vn 0.085757 -0.995911 0.027741 +vn 0.051546 -0.998505 0.016572 +vn 0.028565 -0.999542 0.009095 +vn 0.013916 -0.999878 0.004181 +vn 0.825526 -0.374218 0.422376 +vn 0.607929 -0.730583 0.310862 +vn 0.365764 -0.911618 0.187506 +vn 0.216041 -0.970122 0.110263 +vn 0.129215 -0.989349 0.066530 +vn 0.080233 -0.995911 0.040803 +vn 0.048219 -0.998535 0.024445 +vn 0.026734 -0.999542 0.013459 +vn 0.013062 -0.999878 0.006317 +vn 0.749535 -0.374432 0.545824 +vn 0.551805 -0.730857 0.401654 +vn 0.333293 -0.911100 0.242439 +vn 0.196051 -0.970183 0.142491 +vn 0.118656 -0.989166 0.086123 +vn 0.072848 -0.995941 0.052767 +vn 0.043794 -0.998535 0.031617 +vn 0.024293 -0.999542 0.017426 +vn 0.011902 -0.999878 0.008271 +vn 0.654866 -0.376019 0.655507 +vn 0.482681 -0.730949 0.482406 +vn 0.292856 -0.910428 0.292062 +vn 0.171484 -0.970183 0.171148 +vn 0.103824 -0.989166 0.103488 +vn 0.063723 -0.995941 0.063417 +vn 0.038331 -0.998535 0.038026 +vn 0.021271 -0.999542 0.020997 +vn 0.010468 -0.999878 0.010010 +vn 0.545183 -0.375927 0.749260 +vn 0.401990 -0.730857 0.551561 +vn 0.242836 -0.911100 0.333018 +vn 0.141545 -0.970519 0.194952 +vn 0.086520 -0.989166 0.118381 +vn 0.053133 -0.995941 0.072573 +vn 0.031983 -0.998535 0.043550 +vn 0.017762 -0.999542 0.024049 +vn 0.008789 -0.999878 0.011536 +vn 0.422559 -0.374187 0.825465 +vn 0.311197 -0.730583 0.607746 +vn 0.189062 -0.910276 0.368267 +vn 0.110691 -0.970122 0.215796 +vn 0.067049 -0.989166 0.130528 +vn 0.041200 -0.995911 0.080050 +vn 0.024812 -0.998535 0.048036 +vn 0.013794 -0.999542 0.026551 +vn 0.006897 -0.999878 0.012787 +vn 0.287942 -0.373852 0.881649 +vn 0.212195 -0.730186 0.649434 +vn 0.129093 -0.910062 0.393750 +vn 0.074557 -0.970397 0.229591 +vn 0.045808 -0.989135 0.139622 +vn 0.028169 -0.995911 0.085604 +vn 0.016999 -0.998505 0.051393 +vn 0.009491 -0.999542 0.028413 +vn 0.004822 -0.999878 0.013703 +vn 0.145543 -0.373516 0.916105 +vn 0.107364 -0.729789 0.675161 +vn 0.065584 -0.909879 0.409589 +vn 0.037507 -0.970336 0.238746 +vn 0.023286 -0.989105 0.145268 +vn 0.014344 -0.995911 0.089084 +vn 0.008698 -0.998505 0.053499 +vn 0.004883 -0.999542 0.029603 +vn 0.002594 -0.999878 0.014313 +vn 0.000092 -0.373333 0.927671 +vn 0.000519 -0.728446 0.685080 +vn 0.000214 -0.910520 0.413404 +vn -0.000336 -0.970306 0.241798 +vn 0.000214 -0.989105 0.147191 +vn 0.000214 -0.995911 0.090304 +vn 0.000183 -0.998505 0.054231 +vn 0.000183 -0.999542 0.030030 +vn 0.000305 -0.999878 0.014557 +vn -0.145360 -0.373486 0.916135 +vn -0.106967 -0.729789 0.675222 +vn -0.064852 -0.911222 0.406720 +vn -0.037843 -0.969970 0.240150 +vn -0.022828 -0.989105 0.145329 +vn -0.013916 -0.995911 0.089175 +vn -0.008271 -0.998505 0.053560 +vn -0.004486 -0.999542 0.029664 +vn -0.001953 -0.999878 0.014405 +vn -0.287729 -0.373852 0.881710 +vn -0.211798 -0.730186 0.649556 +vn -0.128025 -0.910062 0.394116 +vn -0.075106 -0.970061 0.230903 +vn -0.045351 -0.989135 0.139744 +vn -0.027741 -0.995911 0.085757 +vn -0.016572 -0.998505 0.051546 +vn -0.009095 -0.999542 0.028565 +vn -0.004181 -0.999878 0.013916 +vn -0.422376 -0.374218 0.825526 +vn -0.310862 -0.730583 0.607929 +vn -0.187628 -0.910977 0.367260 +vn -0.110141 -0.970489 0.214484 +vn -0.066622 -0.989166 0.130741 +vn -0.040803 -0.995911 0.080233 +vn -0.024445 -0.998535 0.048219 +vn -0.013459 -0.999542 0.026734 +vn -0.006317 -0.999878 0.013062 +vn -0.545824 -0.374432 0.749535 +vn -0.401654 -0.730857 0.551805 +vn -0.242439 -0.911100 0.333293 +vn -0.142491 -0.970183 0.196051 +vn -0.086123 -0.989166 0.118656 +vn -0.052767 -0.995941 0.072848 +vn -0.031617 -0.998535 0.043794 +vn -0.017426 -0.999542 0.024293 +vn -0.008271 -0.999878 0.011933 +vn -0.655507 -0.376019 0.654866 +vn -0.482406 -0.730949 0.482681 +vn -0.291177 -0.911130 0.291513 +vn -0.171148 -0.970183 0.171484 +vn -0.103488 -0.989166 0.103824 +vn -0.063417 -0.995941 0.063723 +vn -0.038026 -0.998535 0.038331 +vn -0.020997 -0.999542 0.021271 +vn -0.010010 -0.999878 0.010468 +vn -0.749260 -0.375927 0.545183 +vn -0.551561 -0.730857 0.401990 +vn -0.333018 -0.911100 0.242836 +vn -0.195746 -0.970183 0.142857 +vn -0.118381 -0.989166 0.086520 +vn -0.072573 -0.995941 0.053133 +vn -0.043550 -0.998535 0.031983 +vn -0.024049 -0.999542 0.017762 +vn -0.011536 -0.999878 0.008789 +vn -0.825465 -0.374187 0.422559 +vn -0.607746 -0.730583 0.311197 +vn -0.367046 -0.910977 0.188055 +vn -0.215796 -0.970122 0.110691 +vn -0.130528 -0.989166 0.067049 +vn -0.080050 -0.995911 0.041200 +vn -0.048036 -0.998535 0.024812 +vn -0.026551 -0.999542 0.013794 +vn -0.012787 -0.999878 0.006897 +vn -0.881649 -0.373852 0.287942 +vn -0.649434 -0.730186 0.212195 +vn -0.392376 -0.910794 0.128300 +vn -0.230750 -0.970061 0.075564 +vn -0.139622 -0.989135 0.045808 +vn -0.085604 -0.995911 0.028169 +vn -0.051393 -0.998505 0.016999 +vn -0.028413 -0.999542 0.009491 +vn -0.013703 -0.999878 0.004822 +vn -0.916105 -0.373516 0.145543 +vn -0.675161 -0.729789 0.107364 +vn -0.408094 -0.910581 0.065004 +vn -0.240059 -0.969970 0.038331 +vn -0.145268 -0.989105 0.023286 +vn -0.089084 -0.995911 0.014344 +vn -0.053499 -0.998505 0.008698 +vn -0.029603 -0.999542 0.004883 +vn -0.014313 -0.999878 0.002594 +vn -0.928281 -0.371807 0.000549 +vn -0.683798 -0.729667 0.000183 +vn -0.413404 -0.910520 0.000214 +vn -0.243233 -0.969939 0.000214 +vn -0.147191 -0.989105 0.000214 +vn -0.090304 -0.995911 0.000214 +vn -0.054231 -0.998505 0.000183 +vn -0.030030 -0.999542 0.000183 +vn -0.014557 -0.999878 0.000305 +vn -0.916135 -0.373486 -0.145360 +vn -0.673971 -0.730888 -0.107303 +vn -0.408185 -0.910581 -0.064516 +vn -0.240150 -0.969970 -0.037843 +vn -0.145329 -0.989105 -0.022828 +vn -0.089175 -0.995911 -0.013916 +vn -0.053560 -0.998505 -0.008271 +vn -0.029664 -0.999542 -0.004486 +vn -0.014405 -0.999878 -0.001953 +vn -0.881710 -0.373852 -0.287729 +vn -0.649556 -0.730186 -0.211798 +vn -0.394116 -0.910062 -0.128025 +vn -0.230903 -0.970061 -0.075106 +vn -0.139744 -0.989135 -0.045351 +vn -0.085757 -0.995911 -0.027741 +vn -0.051546 -0.998505 -0.016572 +vn -0.028565 -0.999542 -0.009095 +vn -0.013916 -0.999878 -0.004181 +vn -0.825526 -0.374218 -0.422376 +vn -0.607929 -0.730583 -0.310862 +vn -0.367260 -0.910977 -0.187628 +vn -0.214484 -0.970489 -0.110141 +vn -0.130772 -0.989166 -0.066622 +vn -0.080233 -0.995911 -0.040803 +vn -0.048219 -0.998535 -0.024445 +vn -0.026734 -0.999542 -0.013459 +vn -0.013062 -0.999878 -0.006317 +vn -0.749535 -0.374432 -0.545824 +vn -0.553026 -0.729667 -0.402142 +vn -0.333293 -0.911100 -0.242439 +vn -0.196051 -0.970183 -0.142491 +vn -0.118656 -0.989166 -0.086123 +vn -0.072848 -0.995941 -0.052767 +vn -0.043794 -0.998535 -0.031617 +vn -0.024293 -0.999542 -0.017426 +vn -0.011902 -0.999878 -0.008271 +vn -0.655690 -0.374554 -0.655538 +vn -0.482681 -0.730949 -0.482406 +vn -0.290139 -0.911771 -0.290597 +vn -0.171484 -0.970183 -0.171148 +vn -0.103824 -0.989166 -0.103488 +vn -0.063723 -0.995941 -0.063417 +vn -0.038331 -0.998535 -0.038026 +vn -0.021271 -0.999542 -0.020997 +vn -0.010468 -0.999878 -0.010010 +vn -0.545976 -0.374462 -0.749413 +vn -0.402997 -0.729667 -0.552385 +vn -0.242836 -0.911100 -0.333018 +vn -0.142857 -0.970183 -0.195746 +vn -0.086520 -0.989166 -0.118381 +vn -0.053133 -0.995941 -0.072573 +vn -0.031983 -0.998535 -0.043550 +vn -0.017762 -0.999542 -0.024049 +vn -0.008789 -0.999878 -0.011536 +vn -0.422559 -0.374187 -0.825465 +vn -0.311197 -0.730583 -0.607746 +vn -0.187933 -0.910886 -0.367290 +vn -0.110691 -0.970122 -0.215796 +vn -0.067049 -0.989166 -0.130528 +vn -0.041200 -0.995911 -0.080020 +vn -0.024812 -0.998535 -0.048036 +vn -0.013794 -0.999542 -0.026551 +vn -0.006897 -0.999878 -0.012787 +vn -0.287942 -0.373852 -0.881649 +vn -0.212195 -0.730186 -0.649434 +vn -0.128300 -0.910794 -0.392376 +vn -0.075381 -0.970000 -0.230995 +vn -0.045808 -0.989135 -0.139622 +vn -0.028169 -0.995911 -0.085604 +vn -0.016999 -0.998505 -0.051393 +vn -0.009491 -0.999542 -0.028413 +vn -0.004822 -0.999878 -0.013703 +vn -0.145543 -0.373516 -0.916105 +vn -0.107364 -0.729789 -0.675161 +vn -0.065004 -0.910581 -0.408094 +vn -0.038331 -0.969970 -0.240059 +vn -0.022462 -0.989319 -0.143956 +vn -0.014344 -0.995911 -0.089084 +vn -0.008698 -0.998505 -0.053499 +vn -0.004883 -0.999542 -0.029603 +vn -0.002594 -0.999878 -0.014313 +vn -0.000549 -0.371807 -0.928281 +vn -0.000519 -0.728446 -0.685080 +vn -0.000214 -0.910520 -0.413434 +vn -0.000214 -0.969939 -0.243233 +vn -0.000214 -0.989105 -0.147191 +vn -0.000214 -0.995911 -0.090304 +vn -0.000183 -0.998505 -0.054231 +vn -0.000183 -0.999542 -0.030030 +vn -0.000305 -0.999878 -0.014557 +vn 0.145360 -0.373486 -0.916135 +vn 0.107303 -0.730888 -0.673971 +vn 0.064852 -0.911222 -0.406720 +vn 0.037751 -0.969573 -0.241768 +vn 0.022828 -0.989105 -0.145329 +vn 0.013916 -0.995911 -0.089175 +vn 0.008271 -0.998505 -0.053560 +vn 0.004486 -0.999542 -0.029664 +vn 0.001953 -0.999878 -0.014405 +vn 0.287851 -0.373791 -0.881680 +vn 0.211798 -0.730186 -0.649556 +vn 0.128025 -0.910062 -0.394116 +vn 0.075106 -0.970061 -0.230903 +vn 0.045473 -0.989349 -0.138218 +vn 0.027741 -0.995911 -0.085757 +vn 0.016572 -0.998505 -0.051546 +vn 0.009095 -0.999542 -0.028565 +vn 0.004181 -0.999878 -0.013916 +vn 0.422376 -0.374218 -0.825526 +vn 0.310800 -0.731681 -0.606647 +vn 0.187628 -0.910977 -0.367260 +vn 0.110538 -0.970092 -0.216071 +vn 0.066622 -0.989166 -0.130772 +vn 0.040803 -0.995911 -0.080233 +vn 0.024445 -0.998535 -0.048219 +vn 0.013459 -0.999542 -0.026734 +vn 0.006317 -0.999878 -0.013062 +vn 0.545824 -0.374432 -0.749535 +vn 0.401654 -0.730857 -0.551775 +vn 0.242439 -0.911100 -0.333293 +vn 0.142491 -0.970183 -0.196051 +vn 0.085788 -0.989380 -0.117191 +vn 0.052767 -0.995941 -0.072848 +vn 0.031617 -0.998535 -0.043794 +vn 0.017426 -0.999542 -0.024293 +vn 0.008271 -0.999878 -0.011902 +vn 0.655538 -0.374554 -0.655690 +vn 0.482406 -0.730949 -0.482681 +vn 0.291177 -0.911130 -0.291513 +vn 0.171148 -0.970183 -0.171484 +vn 0.103488 -0.989166 -0.103824 +vn 0.063417 -0.995941 -0.063723 +vn 0.038026 -0.998535 -0.038331 +vn 0.020997 -0.999542 -0.021271 +vn 0.010010 -0.999878 -0.010468 +vn 0.749413 -0.374462 -0.545976 +vn 0.551561 -0.730857 -0.401990 +vn 0.333018 -0.911100 -0.242836 +vn 0.195746 -0.970183 -0.142857 +vn 0.118381 -0.989166 -0.086520 +vn 0.072573 -0.995941 -0.053133 +vn 0.043550 -0.998535 -0.031983 +vn 0.024049 -0.999542 -0.017762 +vn 0.011536 -0.999878 -0.008789 +vn 0.825800 -0.372662 -0.423231 +vn 0.607746 -0.730583 -0.311197 +vn 0.368267 -0.910276 -0.189062 +vn 0.215796 -0.970122 -0.110691 +vn 0.130528 -0.989166 -0.067049 +vn 0.080020 -0.995911 -0.041200 +vn 0.048036 -0.998535 -0.024812 +vn 0.026551 -0.999542 -0.013794 +vn 0.012787 -0.999878 -0.006897 +vn 0.881649 -0.373852 -0.287942 +vn 0.648457 -0.731284 -0.211341 +vn 0.392376 -0.910794 -0.128300 +vn 0.230995 -0.970000 -0.075381 +vn 0.139622 -0.989135 -0.045808 +vn 0.085604 -0.995911 -0.028169 +vn 0.051393 -0.998505 -0.016999 +vn 0.028413 -0.999542 -0.009491 +vn 0.013703 -0.999878 -0.004822 +vn 0.916105 -0.373516 -0.145543 +vn 0.676351 -0.728599 -0.107883 +vn 0.408094 -0.910581 -0.065004 +vn 0.240059 -0.969970 -0.038331 +vn 0.143956 -0.989319 -0.022462 +vn 0.089084 -0.995911 -0.014344 +vn 0.053499 -0.998505 -0.008698 +vn 0.029603 -0.999542 -0.004883 +vn 0.014313 -0.999878 -0.002594 +vn 0.001099 -0.999969 0.003693 +vn 0.001099 -0.984710 0.174108 +vn 0.004791 -0.985717 0.168279 +vn 0.004761 -0.999969 -0.001984 +vn 0.017487 -0.985260 0.170049 +vn 0.017426 -0.999847 0.000000 +vn 0.042421 -0.984497 0.170080 +vn 0.042177 -0.999084 0.000000 +vn 0.085482 -0.981506 0.171117 +vn 0.084841 -0.996368 0.000000 +vn 0.156377 -0.972167 0.174352 +vn 0.155126 -0.987884 0.000061 +vn 0.270974 -0.945280 0.181555 +vn 0.269143 -0.963073 0.000305 +vn 0.449141 -0.872005 0.194494 +vn 0.448805 -0.893613 0.000549 +vn 0.686972 -0.695547 0.210334 +vn 0.694754 -0.719199 0.000366 +vn 0.898312 -0.380535 0.219550 +vn 0.918332 -0.395795 -0.000336 +vn 0.973876 -0.052339 0.220862 +vn 0.998474 -0.055177 -0.000397 +vn 0.001068 -0.924345 0.381512 +vn 0.004730 -0.926695 0.375713 +vn 0.017151 -0.926054 0.376965 +vn 0.041627 -0.925321 0.376843 +vn 0.083834 -0.921628 0.378826 +vn 0.152867 -0.910062 0.385174 +vn 0.262612 -0.878475 0.399060 +vn 0.427625 -0.799097 0.422498 +vn 0.637867 -0.625294 0.449538 +vn 0.818690 -0.336894 0.464949 +vn 0.882473 -0.044618 0.468215 +vn 0.000946 -0.778222 0.627949 +vn 0.004212 -0.779992 0.625721 +vn 0.015259 -0.779321 0.626392 +vn 0.036988 -0.778771 0.626179 +vn 0.074282 -0.774255 0.628437 +vn 0.134373 -0.760094 0.635731 +vn 0.227027 -0.724387 0.650899 +vn 0.359722 -0.644612 0.674551 +vn 0.519517 -0.490097 0.699911 +vn 0.651570 -0.256661 0.713828 +vn 0.695181 -0.031800 0.718101 +vn 0.000610 -0.470199 0.882534 +vn 0.002716 -0.474929 0.880001 +vn 0.009857 -0.476394 0.879147 +vn 0.023957 -0.477065 0.878506 +vn 0.048006 -0.474685 0.878842 +vn 0.086245 -0.464766 0.881191 +vn 0.143681 -0.439070 0.886868 +vn 0.222633 -0.383679 0.896207 +vn 0.312906 -0.283242 0.906552 +vn 0.382641 -0.142857 0.912748 +vn 0.399304 -0.016083 0.916654 +vn 0.000000 0.004303 0.999969 +vn 0.000000 -0.001679 0.999969 +vn -0.000031 0.001343 0.999969 +vn -0.000183 0.002625 0.999969 +vn -0.000641 0.004120 0.999969 +vn -0.001526 0.005219 0.999969 +vn -0.002777 0.004975 0.999969 +vn -0.003571 0.002808 0.999969 +vn -0.002655 0.000122 0.999969 +vn -0.000122 -0.000763 0.999969 +vn -0.001221 -0.000244 0.999969 +vn -0.000671 0.474105 0.880428 +vn -0.003021 0.473861 0.880551 +vn -0.011048 0.478256 0.878140 +vn -0.027070 0.479690 0.877010 +vn -0.054689 0.476730 0.877316 +vn -0.098453 0.463179 0.880764 +vn -0.162206 0.428663 0.888760 +vn -0.243599 0.359935 0.900601 +vn -0.325816 0.251350 0.911374 +vn -0.380779 0.121372 0.916623 +vn -0.394910 0.011841 0.918607 +vn -0.001160 0.778466 0.627674 +vn -0.005158 0.779199 0.626728 +vn -0.018708 0.780053 0.625416 +vn -0.045686 0.778466 0.625965 +vn -0.091952 0.770318 0.630970 +vn -0.165563 0.747459 0.643300 +vn -0.274270 0.694510 0.665120 +vn -0.415693 0.588427 0.693442 +vn -0.560228 0.415265 0.716697 +vn -0.658681 0.202765 0.724570 +vn -0.690573 0.019166 0.722983 +vn -0.001434 0.927335 0.374187 +vn -0.006287 0.926115 0.377148 +vn -0.022828 0.926115 0.376507 +vn -0.055757 0.924131 0.377941 +vn -0.112705 0.916593 0.383587 +vn -0.204505 0.894955 0.396435 +vn -0.343211 0.840754 0.418683 +vn -0.527879 0.721885 0.447401 +vn -0.717154 0.514115 0.470473 +vn -0.842311 0.251137 0.476852 +vn -0.880612 0.022553 0.473251 +vn -0.001526 0.986023 0.166570 +vn -0.006684 0.985046 0.172033 +vn -0.024659 0.985107 0.170019 +vn -0.060305 0.983367 0.171148 +vn -0.122196 0.976989 0.174688 +vn -0.222846 0.957671 0.182134 +vn -0.377056 0.905454 0.194708 +vn -0.585070 0.783013 0.211035 +vn -0.798151 0.559282 0.223853 +vn -0.935270 0.271859 0.226600 +vn -0.974395 0.023041 0.223609 +vn -0.001556 0.999969 -0.003723 +vn -0.006806 0.999969 0.001984 +vn -0.025147 0.999664 0.000153 +vn -0.061617 0.998077 0.000366 +vn -0.124943 0.992157 0.000671 +vn -0.228370 0.973540 0.001007 +vn -0.387371 0.921903 0.001312 +vn -0.602557 0.798029 0.001526 +vn -0.822352 0.568926 0.001556 +vn -0.961486 0.274728 0.001282 +vn -0.999725 0.022217 0.000458 +vn -0.001526 0.984710 -0.174108 +vn -0.006684 0.985717 -0.168157 +vn -0.024628 0.985137 -0.169866 +vn -0.060335 0.983490 -0.170537 +vn -0.122379 0.977172 -0.173467 +vn -0.223457 0.957884 -0.180212 +vn -0.378491 0.905393 -0.192206 +vn -0.587695 0.781854 -0.207984 +vn -0.801355 0.556108 -0.220283 +vn -0.937223 0.267922 -0.223121 +vn -0.974761 0.021485 -0.222083 +vn -0.001404 0.925810 -0.377941 +vn -0.006195 0.926786 -0.375469 +vn -0.022736 0.925993 -0.376812 +vn -0.055605 0.924131 -0.377972 +vn -0.112400 0.916776 -0.383160 +vn -0.204169 0.895535 -0.395367 +vn -0.343364 0.841609 -0.416852 +vn -0.529527 0.722251 -0.444838 +vn -0.720847 0.512223 -0.466842 +vn -0.846004 0.247108 -0.472396 +vn -0.881924 0.020539 -0.470901 +vn -0.001160 0.778222 -0.627949 +vn -0.005158 0.778893 -0.627094 +vn -0.018586 0.779351 -0.626270 +vn -0.045320 0.777642 -0.627033 +vn -0.091159 0.769646 -0.631886 +vn -0.164098 0.747337 -0.643818 +vn -0.272378 0.695425 -0.664937 +vn -0.414869 0.590228 -0.692434 +vn -0.562334 0.416028 -0.714591 +vn -0.662954 0.200842 -0.721183 +vn -0.692740 0.017701 -0.720939 +vn -0.000671 0.473800 -0.880612 +vn -0.003021 0.475082 -0.879910 +vn -0.010926 0.476455 -0.879116 +vn -0.026612 0.476455 -0.878780 +vn -0.053377 0.472030 -0.879940 +vn -0.095523 0.457747 -0.883908 +vn -0.157262 0.424146 -0.891812 +vn -0.237709 0.358104 -0.902890 +vn -0.321940 0.251778 -0.912656 +vn -0.381146 0.121525 -0.916471 +vn -0.394452 0.011170 -0.918821 +vn 0.000000 -0.004303 -0.999969 +vn 0.000000 -0.000336 -0.999969 +vn 0.000031 -0.001160 -0.999969 +vn 0.000153 -0.002228 -0.999969 +vn 0.000458 -0.003540 -0.999969 +vn 0.001099 -0.004730 -0.999969 +vn 0.002136 -0.005341 -0.999969 +vn 0.003357 -0.004791 -0.999969 +vn 0.003845 -0.002899 -0.999969 +vn 0.002258 -0.000732 -0.999969 +vn 0.000397 -0.000031 -0.999969 +vn 0.000610 -0.477676 -0.878506 +vn 0.002716 -0.473708 -0.880673 +vn 0.009949 -0.477981 -0.878292 +vn 0.024293 -0.479659 -0.877102 +vn 0.048891 -0.477950 -0.877010 +vn 0.088198 -0.467818 -0.879391 +vn 0.146977 -0.440138 -0.885800 +vn 0.226661 -0.380688 -0.896451 +vn 0.314951 -0.276284 -0.907987 +vn 0.379772 -0.137333 -0.914792 +vn 0.396619 -0.015412 -0.917814 +vn 0.000946 -0.780877 -0.624653 +vn 0.004212 -0.777825 -0.628437 +vn 0.015320 -0.780175 -0.625355 +vn 0.037172 -0.779962 -0.624714 +vn 0.074618 -0.775719 -0.626606 +vn 0.135014 -0.761711 -0.633656 +vn 0.228278 -0.725639 -0.649068 +vn 0.361797 -0.643727 -0.674306 +vn 0.520676 -0.484817 -0.702719 +vn 0.647816 -0.251015 -0.719230 +vn 0.691733 -0.031220 -0.721458 +vn 0.001068 -0.927305 -0.374218 +vn 0.004730 -0.925321 -0.379101 +vn 0.017182 -0.926328 -0.376263 +vn 0.041566 -0.925657 -0.375988 +vn 0.083529 -0.922147 -0.377667 +vn 0.152074 -0.910916 -0.383496 +vn 0.260994 -0.879971 -0.396832 +vn 0.425611 -0.801141 -0.420698 +vn 0.636464 -0.625813 -0.450789 +vn 0.816340 -0.335276 -0.470260 +vn 0.880459 -0.044710 -0.471938 +vn 0.001099 -0.986023 -0.166570 +vn 0.004791 -0.985046 -0.172124 +vn 0.017457 -0.985290 -0.169836 +vn 0.042238 -0.984527 -0.169866 +vn 0.084841 -0.981628 -0.170812 +vn 0.154790 -0.972533 -0.173711 +vn 0.267769 -0.946440 -0.180303 +vn 0.444197 -0.874905 -0.192785 +vn 0.682485 -0.700034 -0.209967 +vn 0.896512 -0.383312 -0.222022 +vn 0.973357 -0.053102 -0.222907 +vn 0.963652 0.146306 0.223426 +vn 0.988708 0.149663 0.000122 +vn 0.937437 0.266396 0.224006 +vn 0.962462 0.271401 0.000305 +vn 0.902829 0.368908 0.220862 +vn 0.927152 0.374615 0.000122 +vn 0.860805 0.461959 0.213446 +vn 0.883724 0.467971 -0.000214 +vn 0.810602 0.549516 0.202338 +vn 0.831446 0.555559 -0.000641 +vn 0.750755 0.632893 0.189093 +vn 0.769158 0.639027 -0.001007 +vn 0.680074 0.711661 0.176092 +vn 0.695761 0.718253 -0.001221 +vn 0.597919 0.783990 0.166814 +vn 0.610706 0.791833 -0.001282 +vn 0.504776 0.847285 0.165136 +vn 0.514389 0.857540 -0.001099 +vn 0.455977 0.874264 0.166509 +vn 0.870846 0.132908 0.473220 +vn 0.846065 0.244179 0.473800 +vn 0.815424 0.340678 0.467971 +vn 0.779992 0.429701 0.454909 +vn 0.738609 0.514786 0.435224 +vn 0.688955 0.596728 0.411298 +vn 0.628712 0.674123 0.387555 +vn 0.556444 0.743736 0.370373 +vn 0.472274 0.801202 0.367382 +vn 0.426862 0.824610 0.371136 +vn 0.682211 0.104740 0.723594 +vn 0.661489 0.194739 0.724204 +vn 0.638539 0.274789 0.718833 +vn 0.614612 0.350688 0.706534 +vn 0.588336 0.425703 0.687460 +vn 0.556627 0.500198 0.663259 +vn 0.515732 0.571673 0.638081 +vn 0.462569 0.634693 0.618976 +vn 0.395978 0.681600 0.615253 +vn 0.357219 0.697226 0.621448 +vn 0.386944 0.059786 0.920133 +vn 0.374096 0.112491 0.920530 +vn 0.361461 0.160619 0.918424 +vn 0.350047 0.207617 0.913419 +vn 0.338939 0.255715 0.905362 +vn 0.326029 0.305307 0.894681 +vn 0.307962 0.354320 0.882931 +vn 0.281228 0.397351 0.873501 +vn 0.243477 0.425977 0.871334 +vn 0.216254 0.426984 0.877987 +vn -0.000641 -0.000153 0.999969 +vn -0.002258 -0.000916 0.999969 +vn -0.003754 -0.002136 0.999969 +vn -0.005066 -0.003815 0.999969 +vn -0.006012 -0.005676 0.999939 +vn -0.006226 -0.007263 0.999939 +vn -0.005310 -0.007660 0.999939 +vn -0.003204 -0.005799 0.999969 +vn -0.000488 -0.001434 0.999969 +vn 0.000336 0.000610 0.999969 +vn -0.386761 -0.060945 0.920133 +vn -0.376812 -0.119449 0.918516 +vn -0.362255 -0.173925 0.915677 +vn -0.347056 -0.226020 0.910184 +vn -0.331248 -0.276620 0.902066 +vn -0.313486 -0.325022 0.892209 +vn -0.291757 -0.368480 0.882656 +vn -0.264290 -0.402417 0.876461 +vn -0.230384 -0.421247 0.877163 +vn -0.208289 -0.420301 0.883114 +vn -0.683096 -0.108554 0.722190 +vn -0.659383 -0.212012 0.721274 +vn -0.626606 -0.306986 0.716300 +vn -0.589435 -0.393384 0.705527 +vn -0.550005 -0.471511 0.689291 +vn -0.508560 -0.541124 0.669698 +vn -0.464705 -0.600940 0.650288 +vn -0.417920 -0.648640 0.636067 +vn -0.368023 -0.681173 0.632862 +vn -0.340068 -0.691855 0.636891 +vn -0.870632 -0.138890 0.471877 +vn -0.838710 -0.272195 0.471633 +vn -0.791681 -0.393475 0.467299 +vn -0.735679 -0.499893 0.456984 +vn -0.675314 -0.590869 0.441359 +vn -0.613697 -0.666921 0.422529 +vn -0.552507 -0.728965 0.404126 +vn -0.492721 -0.777734 0.390271 +vn -0.434797 -0.813776 0.385571 +vn -0.404614 -0.828394 0.387341 +vn -0.962645 -0.153783 0.222755 +vn -0.926756 -0.302194 0.223121 +vn -0.871792 -0.436964 0.221320 +vn -0.804590 -0.553026 0.216163 +vn -0.731803 -0.649007 0.207862 +vn -0.658498 -0.726127 0.197729 +vn -0.587939 -0.786798 0.187628 +vn -0.521805 -0.833888 0.179693 +vn -0.460799 -0.869808 0.176122 +vn -0.430555 -0.885159 0.176183 +vn -0.987457 -0.157689 0.000061 +vn -0.950560 -0.310465 0.000336 +vn -0.893277 -0.449446 0.001068 +vn -0.822565 -0.568621 0.001892 +vn -0.745811 -0.666128 0.002564 +vn -0.668844 -0.743370 0.002991 +vn -0.595508 -0.803308 0.003052 +vn -0.527726 -0.849391 0.002686 +vn -0.466201 -0.884640 0.001892 +vn -0.436872 -0.899503 0.001465 +vn -0.962676 -0.153478 -0.222816 +vn -0.926878 -0.301950 -0.222846 +vn -0.872036 -0.437330 -0.219703 +vn -0.804682 -0.554277 -0.212714 +vn -0.731498 -0.650990 -0.202673 +vn -0.657735 -0.728507 -0.191290 +vn -0.586810 -0.789300 -0.180639 +vn -0.520524 -0.836085 -0.173132 +vn -0.459578 -0.871487 -0.170934 +vn -0.430860 -0.886013 -0.171209 +vn -0.870479 -0.138249 -0.472365 +vn -0.838557 -0.271004 -0.472610 +vn -0.792047 -0.392529 -0.467452 +vn -0.736534 -0.499893 -0.455611 +vn -0.676382 -0.592120 -0.438002 +vn -0.614612 -0.669424 -0.417219 +vn -0.552995 -0.732353 -0.397229 +vn -0.492630 -0.781549 -0.382672 +vn -0.434248 -0.817408 -0.378460 +vn -0.405927 -0.831507 -0.379162 +vn -0.682485 -0.107791 -0.722861 +vn -0.657826 -0.209967 -0.723258 +vn -0.625233 -0.304209 -0.718680 +vn -0.588702 -0.390515 -0.707724 +vn -0.550127 -0.469497 -0.690573 +vn -0.509507 -0.540788 -0.669240 +vn -0.466201 -0.602741 -0.647542 +vn -0.419477 -0.652516 -0.631031 +vn -0.369213 -0.686514 -0.626362 +vn -0.342906 -0.697989 -0.628620 +vn -0.387463 -0.060762 -0.919858 +vn -0.373302 -0.117191 -0.920255 +vn -0.356822 -0.169439 -0.918638 +vn -0.340403 -0.219123 -0.914365 +vn -0.324290 -0.267739 -0.907254 +vn -0.307321 -0.315378 -0.897794 +vn -0.287423 -0.360118 -0.887478 +vn -0.262429 -0.397717 -0.879147 +vn -0.230873 -0.422071 -0.876644 +vn -0.210395 -0.424726 -0.880490 +vn -0.001587 -0.000031 -0.999969 +vn 0.001831 0.000671 -0.999969 +vn 0.003510 0.001831 -0.999969 +vn 0.005188 0.003510 -0.999969 +vn 0.006500 0.005524 -0.999939 +vn 0.007080 0.007447 -0.999939 +vn 0.006500 0.008301 -0.999939 +vn 0.004578 0.007080 -0.999939 +vn 0.001801 0.003327 -0.999969 +vn 0.000183 0.000336 -0.999969 +vn 0.387890 0.060213 -0.919706 +vn 0.376965 0.114200 -0.919126 +vn 0.365307 0.163945 -0.916318 +vn 0.354381 0.212683 -0.910550 +vn 0.343120 0.262215 -0.901914 +vn 0.328990 0.312235 -0.891201 +vn 0.308695 0.359691 -0.880490 +vn 0.279397 0.398785 -0.873409 +vn 0.240150 0.422285 -0.874050 +vn 0.213172 0.421216 -0.881527 +vn 0.683096 0.105197 -0.722678 +vn 0.663686 0.196204 -0.721763 +vn 0.640584 0.276955 -0.716178 +vn 0.615864 0.353191 -0.704215 +vn 0.588275 0.427808 -0.686178 +vn 0.555010 0.501053 -0.663991 +vn 0.512772 0.570482 -0.641560 +vn 0.459029 0.631092 -0.625263 +vn 0.392926 0.675954 -0.623432 +vn 0.353099 0.689840 -0.631977 +vn 0.871303 0.133091 -0.472304 +vn 0.847163 0.244606 -0.471664 +vn 0.816248 0.340953 -0.466323 +vn 0.780297 0.429426 -0.454573 +vn 0.738395 0.513688 -0.436842 +vn 0.688406 0.594745 -0.415143 +vn 0.628132 0.671285 -0.393384 +vn 0.556139 0.740379 -0.377453 +vn 0.472304 0.797754 -0.374767 +vn 0.424543 0.820887 -0.381939 +vn 0.963805 0.146184 -0.222907 +vn 0.937834 0.265938 -0.222877 +vn 0.903378 0.367809 -0.220374 +vn 0.861568 0.460158 -0.214209 +vn 0.811701 0.547044 -0.204535 +vn 0.752312 0.629994 -0.192572 +vn 0.682028 0.708670 -0.180425 +vn 0.600024 0.781396 -0.171270 +vn 0.506516 0.845454 -0.169195 +vn 0.455184 0.873287 -0.173528 +vn -0.081393 0.996673 0.000855 +vn -0.082766 0.958190 0.273873 +vn -0.182226 0.949553 0.255135 +vn -0.186468 0.982452 -0.000977 +vn -0.402203 0.882717 0.242836 +vn -0.419111 0.907926 -0.000519 +vn -0.629597 0.742119 0.229865 +vn -0.655354 0.755303 0.000061 +vn -0.798364 0.561693 0.216987 +vn -0.826533 0.562883 0.000427 +vn -0.884854 0.413526 0.214454 +vn -0.913694 0.406323 0.000946 +vn -0.909207 0.344096 0.234291 +vn -0.943571 0.331065 0.002930 +vn -0.890835 0.363903 0.271859 +vn -0.936766 0.349620 0.014252 +vn -0.833552 0.458388 0.308298 +vn -0.890744 0.454207 0.014954 +vn -0.723075 0.607318 0.329051 +vn -0.782708 0.622211 0.013184 +vn -0.592547 0.740379 0.317301 +vn -0.637226 0.770623 0.007019 +vn -0.079073 0.828822 0.553880 +vn -0.158818 0.837275 0.523148 +vn -0.341258 0.792840 0.504868 +vn -0.538743 0.688406 0.485580 +vn -0.694266 0.547166 0.467513 +vn -0.776421 0.427320 0.463149 +vn -0.790887 0.369213 0.487960 +vn -0.760033 0.379162 0.527757 +vn -0.690176 0.443831 0.571490 +vn -0.585833 0.548173 0.596851 +vn -0.483108 0.654042 0.582049 +vn -0.060945 0.606189 0.792962 +vn -0.110752 0.633778 0.765526 +vn -0.227912 0.620045 0.750694 +vn -0.365642 0.567400 0.737754 +vn -0.485092 0.487472 0.725944 +vn -0.553758 0.413190 0.722892 +vn -0.564989 0.371014 0.736930 +vn -0.543352 0.368664 0.754204 +vn -0.484054 0.398267 0.779107 +vn -0.406262 0.451003 0.794671 +vn -0.338633 0.521653 0.783044 +vn -0.024323 0.317881 0.947783 +vn -0.033418 0.343425 0.938566 +vn -0.060976 0.360179 0.930876 +vn -0.109073 0.365307 0.924436 +vn -0.163427 0.353038 0.921201 +vn -0.201972 0.330699 0.921842 +vn -0.225593 0.313120 0.922513 +vn -0.242714 0.306986 0.920225 +vn -0.239753 0.310831 0.919706 +vn -0.211859 0.313761 0.925535 +vn -0.183477 0.345561 0.920255 +vn 0.029420 -0.002747 0.999542 +vn 0.069247 -0.005249 0.997559 +vn 0.146214 0.025880 0.988891 +vn 0.211463 0.077822 0.974273 +vn 0.233345 0.136113 0.962798 +vn 0.199469 0.180486 0.963103 +vn 0.154973 0.190344 0.969390 +vn 0.100253 0.178869 0.978729 +vn 0.053865 0.147160 0.987640 +vn 0.026612 0.081210 0.996338 +vn -0.005036 0.087649 0.996124 +vn 0.095676 -0.329173 0.939390 +vn 0.167943 -0.320200 0.932310 +vn 0.318949 -0.267403 0.909238 +vn 0.454543 -0.172185 0.873897 +vn 0.532823 -0.068636 0.843410 +vn 0.538591 0.008087 0.842524 +vn 0.483657 0.040345 0.874294 +vn 0.398358 0.023621 0.916898 +vn 0.303903 -0.037751 0.951933 +vn 0.224891 -0.160924 0.960997 +vn 0.178228 -0.247139 0.952422 +vn 0.161290 -0.612445 0.773858 +vn 0.254555 -0.591357 0.765160 +vn 0.456099 -0.512528 0.727500 +vn 0.629749 -0.375011 0.680258 +vn 0.731956 -0.231910 0.640645 +vn 0.758080 -0.133488 0.638325 +vn 0.723106 -0.100070 0.683432 +vn 0.645161 -0.135929 0.751854 +vn 0.533952 -0.235725 0.811945 +vn 0.408856 -0.402478 0.819025 +vn 0.316172 -0.530168 0.786706 +vn 0.213813 -0.819300 0.531938 +vn 0.319926 -0.789575 0.523606 +vn 0.546251 -0.682028 0.486190 +vn 0.735374 -0.511155 0.444838 +vn 0.844966 -0.341044 0.411878 +vn 0.882199 -0.232063 0.409680 +vn 0.869137 -0.206336 0.449385 +vn 0.815271 -0.267678 0.513443 +vn 0.710349 -0.406873 0.574297 +vn 0.552049 -0.599200 0.579791 +vn 0.410108 -0.739341 0.533952 +vn 0.244972 -0.933653 0.261177 +vn 0.357341 -0.898373 0.255318 +vn 0.591540 -0.771783 0.233192 +vn 0.785119 -0.582446 0.210456 +vn 0.896207 -0.399304 0.193152 +vn 0.938536 -0.286966 0.191748 +vn 0.938871 -0.268899 0.214911 +vn 0.903012 -0.349040 0.250343 +vn 0.808039 -0.515244 0.285531 +vn 0.631947 -0.719596 0.287698 +vn 0.454939 -0.852901 0.256050 +vn 0.254463 -0.967071 -0.000244 +vn 0.370037 -0.929014 0.001434 +vn 0.604816 -0.796350 -0.000397 +vn 0.798944 -0.601367 -0.001343 +vn 0.909970 -0.414655 -0.002564 +vn 0.953154 -0.302347 -0.005493 +vn 0.956847 -0.290536 -0.004517 +vn 0.925108 -0.379589 -0.004700 +vn 0.831263 -0.555834 -0.003937 +vn 0.648000 -0.761620 -0.001984 +vn 0.464156 -0.885739 0.000580 +vn 0.244331 -0.932463 -0.266060 +vn 0.360942 -0.898099 -0.251137 +vn 0.594043 -0.769463 -0.234504 +vn 0.787194 -0.578387 -0.213813 +vn 0.897153 -0.394116 -0.199286 +vn 0.937284 -0.282022 -0.204749 +vn 0.935270 -0.269234 -0.229713 +vn 0.896969 -0.353832 -0.264931 +vn 0.800134 -0.522477 -0.294504 +vn 0.624104 -0.726707 -0.286935 +vn 0.455397 -0.855098 -0.247688 +vn 0.211615 -0.812311 -0.543443 +vn 0.324656 -0.790979 -0.518571 +vn 0.549211 -0.677664 -0.488937 +vn 0.737083 -0.504929 -0.449110 +vn 0.844172 -0.333964 -0.419294 +vn 0.877041 -0.224891 -0.424482 +vn 0.860317 -0.202582 -0.467757 +vn 0.806330 -0.266060 -0.528184 +vn 0.705924 -0.407086 -0.579577 +vn 0.552355 -0.603992 -0.574480 +vn 0.417249 -0.747215 -0.517197 +vn 0.157781 -0.598926 -0.785058 +vn 0.259011 -0.592853 -0.762505 +vn 0.458022 -0.505448 -0.731223 +vn 0.629292 -0.367534 -0.684744 +vn 0.727928 -0.224372 -0.647877 +vn 0.749138 -0.126164 -0.650258 +vn 0.712668 -0.094394 -0.695090 +vn 0.641011 -0.130741 -0.756279 +vn 0.539262 -0.230750 -0.809870 +vn 0.418195 -0.400372 -0.815332 +vn 0.328806 -0.541215 -0.773888 +vn 0.092746 -0.316172 -0.944151 +vn 0.169073 -0.316141 -0.933500 +vn 0.317911 -0.258950 -0.912046 +vn 0.450514 -0.165685 -0.877224 +vn 0.526292 -0.063936 -0.847865 +vn 0.532456 0.010620 -0.846370 +vn 0.483749 0.041780 -0.874172 +vn 0.408948 0.025513 -0.912168 +vn 0.320231 -0.034242 -0.946715 +vn 0.237678 -0.154820 -0.958892 +vn 0.188635 -0.253395 -0.948759 +vn 0.026704 0.012177 -0.999542 +vn 0.067202 0.004852 -0.997711 +vn 0.141758 0.032716 -0.989349 +vn 0.206244 0.080081 -0.975188 +vn 0.235420 0.131718 -0.962889 +vn 0.221870 0.169103 -0.960295 +vn 0.174017 0.185308 -0.967132 +vn 0.124271 0.174932 -0.976684 +vn 0.074313 0.144383 -0.986694 +vn 0.035279 0.082919 -0.995911 +vn 0.004608 0.075625 -0.997101 +vn -0.027406 0.340892 -0.939695 +vn -0.034486 0.339091 -0.940092 +vn -0.058412 0.351634 -0.934294 +vn -0.099124 0.354686 -0.929685 +vn -0.146214 0.344340 -0.927366 +vn -0.189581 0.327158 -0.925748 +vn -0.212073 0.310007 -0.926756 +vn -0.216315 0.300211 -0.928983 +vn -0.212867 0.300485 -0.929716 +vn -0.192907 0.302133 -0.933531 +vn -0.167852 0.335490 -0.926939 +vn -0.062746 0.628956 -0.774865 +vn -0.112003 0.629627 -0.768761 +vn -0.226661 0.616260 -0.754173 +vn -0.361217 0.565050 -0.741752 +vn -0.484146 0.486709 -0.727073 +vn -0.558550 0.412030 -0.719871 +vn -0.570421 0.370251 -0.733116 +vn -0.536149 0.368786 -0.759270 +vn -0.475448 0.399548 -0.783746 +vn -0.400128 0.454512 -0.795770 +vn -0.323527 0.520768 -0.790002 +vn -0.078921 0.841670 -0.534165 +vn -0.157476 0.833888 -0.528977 +vn -0.338908 0.791009 -0.509323 +vn -0.537431 0.687948 -0.487686 +vn -0.697012 0.547197 -0.463332 +vn -0.780450 0.427534 -0.456160 +vn -0.798395 0.369091 -0.475661 +vn -0.768273 0.378979 -0.515824 +vn -0.696493 0.445875 -0.562151 +vn -0.588946 0.553148 -0.589160 +vn -0.472274 0.657918 -0.586566 +vn -0.081881 0.960875 -0.264534 +vn -0.178899 0.949004 -0.259468 +vn -0.400098 0.882931 -0.245552 +vn -0.628864 0.742668 -0.230049 +vn -0.797937 0.563097 -0.214850 +vn -0.884610 0.416242 -0.210181 +vn -0.911374 0.345622 -0.223426 +vn -0.899960 0.359844 -0.246040 +vn -0.845241 0.453566 -0.282479 +vn -0.734275 0.604846 -0.308115 +vn -0.591937 0.743431 -0.311289 +vn -0.487045 0.825770 0.284341 +vn -0.523179 0.852168 0.005341 +vn -0.410230 0.878323 0.245369 +vn -0.440596 0.897671 0.003540 +vn -0.322306 0.926633 0.193396 +vn -0.345347 0.938444 0.001648 +vn -0.207404 0.970916 0.119388 +vn -0.220405 0.975402 0.000183 +vn 0.028840 0.998627 -0.043153 +vn 0.045991 0.998932 -0.000427 +vn 0.486465 0.810877 -0.325205 +vn 0.597827 0.801599 0.000061 +vn 0.828059 0.178014 -0.531571 +vn 0.991760 0.128025 -0.000610 +vn 0.766106 -0.397382 -0.505081 +vn 0.874203 -0.485488 -0.002441 +vn 0.641835 -0.645894 -0.413312 +vn 0.706992 -0.707205 -0.000732 +vn 0.593890 -0.723289 -0.352275 +vn 0.641102 -0.767296 0.014069 +vn -0.399213 0.746117 0.532792 +vn -0.337260 0.820429 0.461592 +vn -0.266823 0.891598 0.365795 +vn -0.172918 0.958739 0.225532 +vn -0.004975 0.998871 -0.046999 +vn 0.277993 0.838435 -0.468703 +vn 0.512131 0.313913 -0.799463 +vn 0.534227 -0.192511 -0.823084 +vn 0.487686 -0.483077 -0.727134 +vn 0.468368 -0.592792 -0.655110 +vn -0.283395 0.618854 0.732597 +vn -0.242225 0.729118 0.640065 +vn -0.193670 0.838191 0.509781 +vn -0.128849 0.941130 0.312418 +vn -0.027528 0.999359 -0.021882 +vn 0.126560 0.868374 -0.479446 +vn 0.261971 0.461287 -0.847652 +vn 0.310099 0.030824 -0.950194 +vn 0.311869 -0.274331 -0.909635 +vn 0.311289 -0.402783 -0.860714 +vn -0.152409 0.440138 0.884884 +vn -0.132115 0.599872 0.789087 +vn -0.108982 0.764977 0.634724 +vn -0.080660 0.916837 0.391003 +vn -0.036714 0.999176 0.015595 +vn 0.031892 0.895566 -0.443739 +vn 0.096225 0.585192 -0.805139 +vn 0.133122 0.235694 -0.962645 +vn 0.148503 -0.049745 -0.987640 +vn 0.153020 -0.175329 -0.972533 +vn 0.000092 0.189184 0.981933 +vn 0.000275 0.407819 0.913053 +vn -0.007294 0.651875 0.758263 +vn -0.021729 0.878719 0.476791 +vn -0.034333 0.997681 0.058504 +vn -0.031343 0.918668 -0.393750 +vn -0.019379 0.687521 -0.725883 +vn -0.006928 0.422193 -0.906461 +vn 0.000214 0.186102 -0.982513 +vn -0.000092 0.084201 -0.996429 +vn 0.162847 -0.139439 0.976714 +vn 0.158879 0.116214 0.980407 +vn 0.133976 0.454817 0.880428 +vn 0.068331 0.811090 0.580889 +vn -0.018921 0.994201 0.105716 +vn -0.078494 0.938505 -0.336161 +vn -0.107364 0.775506 -0.622089 +vn -0.122349 0.593524 -0.795434 +vn -0.134342 0.427442 -0.893979 +vn -0.142827 0.359355 -0.922178 +vn 0.306131 -0.487320 0.817774 +vn 0.329417 -0.266579 0.905759 +vn 0.334147 0.123600 0.934355 +vn 0.231941 0.673696 0.701651 +vn 0.018128 0.987457 0.156713 +vn -0.116703 0.955962 -0.269265 +vn -0.177831 0.851344 -0.493484 +vn -0.216254 0.743858 -0.632344 +vn -0.246712 0.648366 -0.720206 +vn -0.261177 0.609119 -0.748802 +vn 0.392712 -0.745628 0.538285 +vn 0.450179 -0.620045 0.642506 +vn 0.540788 -0.300150 0.785760 +vn 0.515885 0.418775 0.747276 +vn 0.098636 0.975066 0.198645 +vn -0.148289 0.970489 -0.190039 +vn -0.232917 0.911618 -0.338633 +vn -0.284646 0.857753 -0.427992 +vn -0.324900 0.812250 -0.484420 +vn -0.342357 0.792352 -0.504898 +vn 0.423933 -0.871273 0.247169 +vn 0.492996 -0.816095 0.301492 +vn 0.646443 -0.640858 0.413923 +vn 0.839137 0.172155 0.515915 +vn 0.243599 0.954619 0.171209 +vn -0.170629 0.980468 -0.097476 +vn -0.267312 0.948943 -0.167302 +vn -0.323038 0.922971 -0.209143 +vn -0.365398 0.901181 -0.233070 +vn -0.383343 0.890774 -0.243965 +vn 0.429762 -0.902921 -0.003784 +vn 0.499130 -0.866482 -0.005585 +vn 0.664022 -0.747581 -0.011902 +vn 0.993500 0.113559 -0.002747 +vn 0.339152 0.940703 -0.000427 +vn -0.178411 0.983947 0.000061 +vn -0.278146 0.960509 -0.000061 +vn -0.334910 0.942228 -0.001190 +vn -0.376690 0.926328 0.000336 +vn -0.394330 0.918943 -0.003815 +vn 0.424818 -0.868404 -0.255684 +vn 0.495376 -0.809107 -0.316080 +vn 0.653890 -0.610645 -0.446638 +vn 0.833308 0.198798 -0.515763 +vn 0.265908 0.948210 -0.173711 +vn -0.170568 0.980468 0.097781 +vn -0.266854 0.948882 0.168371 +vn -0.323588 0.922636 0.209784 +vn -0.365520 0.900266 0.236427 +vn -0.384136 0.893246 0.233467 +vn 0.392529 -0.738731 -0.547838 +vn 0.449660 -0.603107 -0.658803 +vn 0.536363 -0.262734 -0.802026 +vn 0.525101 0.400555 -0.750847 +vn 0.128513 0.966033 -0.224067 +vn -0.147282 0.970916 0.188696 +vn -0.232521 0.911405 0.339427 +vn -0.284371 0.856563 0.430555 +vn -0.323832 0.808954 0.490616 +vn -0.345256 0.799890 0.490829 +vn 0.305002 -0.480758 -0.822077 +vn 0.327219 -0.255165 -0.909818 +vn 0.333079 0.128056 -0.934141 +vn 0.248299 0.646321 -0.721519 +vn 0.033448 0.982238 -0.184484 +vn -0.115574 0.957091 0.265694 +vn -0.178045 0.850765 0.494430 +vn -0.216224 0.743126 0.633229 +vn -0.244911 0.644246 0.724509 +vn -0.265938 0.620472 0.737724 +vn 0.163945 -0.142705 -0.976074 +vn 0.160283 0.111942 -0.980682 +vn 0.137577 0.445204 -0.884762 +vn 0.076357 0.798334 -0.597308 +vn -0.012696 0.991913 -0.126072 +vn -0.077792 0.940977 0.329325 +vn -0.106937 0.774407 0.623554 +vn -0.122776 0.592425 0.796167 +vn -0.133915 0.426679 0.894406 +vn -0.146123 0.366894 0.918699 +vn 0.004883 0.179937 -0.983642 +vn 0.002777 0.400800 -0.916135 +vn -0.004975 0.645985 -0.763298 +vn -0.018860 0.874630 -0.484359 +vn -0.032075 0.997009 -0.070132 +vn -0.031800 0.922025 0.385754 +vn -0.018067 0.686117 0.727226 +vn -0.006653 0.417493 0.908628 +vn 0.000122 0.182440 0.983184 +vn 0.000061 0.083468 0.996490 +vn -0.144963 0.435713 -0.888302 +vn -0.127293 0.598590 -0.790857 +vn -0.106662 0.764153 -0.636128 +vn -0.079501 0.916654 -0.391675 +vn -0.035310 0.999146 -0.020386 +vn 0.030519 0.898953 0.436933 +vn 0.098422 0.584094 0.805658 +vn 0.134678 0.227638 0.964354 +vn 0.148381 -0.056398 0.987304 +vn 0.146428 -0.166662 0.975066 +vn -0.278390 0.616779 -0.736229 +vn -0.236976 0.728477 -0.642750 +vn -0.189245 0.839320 -0.509598 +vn -0.126743 0.942778 -0.308298 +vn -0.025422 0.999451 0.020692 +vn 0.125065 0.871242 0.474624 +vn 0.264595 0.462783 0.846034 +vn 0.312235 0.022217 0.949736 +vn 0.310862 -0.279855 0.908292 +vn 0.295785 -0.384472 0.874447 +vn -0.398419 0.746239 -0.533219 +vn -0.335337 0.820368 -0.463149 +vn -0.263253 0.892178 -0.366955 +vn -0.169317 0.960204 -0.222022 +vn -0.002014 0.998932 0.045930 +vn 0.276925 0.840632 0.465407 +vn 0.514298 0.319681 0.795801 +vn 0.535569 -0.196783 0.821223 +vn 0.485763 -0.483444 0.728202 +vn 0.449782 -0.573473 0.684683 +vn -0.489883 0.826411 -0.277444 +vn -0.411908 0.878597 -0.241615 +vn -0.322336 0.926786 -0.192602 +vn -0.205267 0.971465 -0.118564 +vn 0.028748 0.998749 0.040193 +vn 0.483016 0.814325 0.321787 +vn 0.827906 0.189062 0.527970 +vn 0.768212 -0.394696 0.503983 +vn 0.642506 -0.642689 0.417249 +vn 0.583300 -0.713767 0.387585 +vn 0.130131 0.991272 0.020875 +vn 0.131840 0.991241 0.000366 +vn 0.000000 1.000000 0.000000 +vn 0.415082 0.907376 0.065645 +vn 0.420362 0.907346 0.000244 +vn 0.887143 0.439711 0.139988 +vn 0.899014 0.437880 0.000336 +vn 0.951415 -0.268838 0.149937 +vn 0.963195 -0.268715 -0.000061 +vn 0.792627 -0.596728 0.124882 +vn 0.802637 -0.596423 -0.000092 +vn 0.736778 -0.666036 0.116276 +vn 0.746178 -0.665700 0.000031 +vn 0.775842 -0.619037 0.121586 +vn 0.786218 -0.617939 0.000092 +vn 0.908231 -0.393109 0.143254 +vn 0.919584 -0.392834 -0.000488 +vn 0.970702 0.184118 0.154302 +vn 0.983551 0.180548 -0.000336 +vn 0.667135 0.737358 0.105869 +vn 0.675436 0.737388 -0.000183 +vn 0.125065 0.991272 0.040986 +vn 0.399182 0.907590 0.129826 +vn 0.853359 0.441725 0.276833 +vn 0.915983 -0.269204 0.297372 +vn 0.762749 -0.597369 0.247597 +vn 0.709525 -0.666555 0.228523 +vn 0.746605 -0.619739 0.241798 +vn 0.874569 -0.392346 0.284860 +vn 0.934355 0.183996 0.305124 +vn 0.641743 0.737785 0.209235 +vn 0.116916 0.991302 0.059999 +vn 0.373394 0.907834 0.190649 +vn 0.799799 0.440291 0.407971 +vn 0.857265 -0.271218 0.437605 +vn 0.714011 -0.598010 0.363994 +vn 0.663411 -0.667318 0.338420 +vn 0.699088 -0.619587 0.356822 +vn 0.818781 -0.393078 0.418348 +vn 0.874935 0.183905 0.447890 +vn 0.600665 0.738121 0.307108 +vn 0.105960 0.991333 0.077486 +vn 0.337626 0.908719 0.245277 +vn 0.726005 0.440535 0.528001 +vn 0.778832 -0.269875 0.566179 +vn 0.648000 -0.598468 0.471023 +vn 0.602008 -0.667745 0.437757 +vn 0.634327 -0.620136 0.461562 +vn 0.743370 -0.392163 0.541795 +vn 0.794397 0.183844 0.578875 +vn 0.546495 0.737114 0.397443 +vn 0.092502 0.991333 0.093051 +vn 0.295907 0.908078 0.296304 +vn 0.635121 0.438795 0.635639 +vn 0.680898 -0.269967 0.680776 +vn 0.566485 -0.598621 0.566301 +vn 0.526261 -0.667898 0.526200 +vn 0.554552 -0.620319 0.554674 +vn 0.649892 -0.393811 0.650014 +vn 0.695303 0.182043 0.695242 +vn 0.476943 0.738456 0.476638 +vn 0.076876 0.991333 0.106388 +vn 0.245491 0.908719 0.337474 +vn 0.527787 0.440565 0.726157 +vn 0.566912 -0.268197 0.778863 +vn 0.472335 -0.597491 0.647969 +vn 0.437819 -0.667745 0.601978 +vn 0.461867 -0.620960 0.633290 +vn 0.541002 -0.395032 0.742454 +vn 0.578845 0.182104 0.794794 +vn 0.397198 0.738365 0.544969 +vn 0.059328 0.991302 0.117252 +vn 0.190161 0.907834 0.373669 +vn 0.407910 0.438490 0.800806 +vn 0.437513 -0.269570 0.857814 +vn 0.363048 -0.598926 0.713736 +vn 0.336741 -0.667379 0.664205 +vn 0.356731 -0.619648 0.699088 +vn 0.418348 -0.393048 0.818812 +vn 0.447340 0.183996 0.875210 +vn 0.307474 0.738151 0.600452 +vn 0.040254 0.991272 0.125309 +vn 0.129215 0.908322 0.397748 +vn 0.277230 0.441755 0.853206 +vn 0.297525 -0.269204 0.915952 +vn 0.248817 -0.596393 0.763115 +vn 0.230323 -0.666677 0.708853 +vn 0.243568 -0.619770 0.745994 +vn 0.285104 -0.393811 0.873836 +vn 0.304544 0.184057 0.934507 +vn 0.209632 0.737815 0.641591 +vn 0.020112 0.991272 0.130253 +vn 0.065279 0.908109 0.413556 +vn 0.139622 0.437910 0.888089 +vn 0.149541 -0.270455 0.951018 +vn 0.126011 -0.595752 0.793176 +vn 0.114322 -0.666097 0.737022 +vn 0.123447 -0.619068 0.775536 +vn 0.143315 -0.390332 0.909421 +vn 0.153691 0.184149 0.970794 +vn 0.106082 0.737480 0.666951 +vn -0.000366 0.991241 0.131840 +vn 0.000122 0.908078 0.418775 +vn -0.000336 0.437880 0.899014 +vn 0.000458 -0.267037 0.963683 +vn 0.000092 -0.596423 0.802637 +vn -0.001923 -0.665792 0.746117 +vn -0.000122 -0.617908 0.786218 +vn -0.000610 -0.389996 0.920804 +vn 0.000031 0.182409 0.983215 +vn 0.000183 0.737388 0.675436 +vn -0.020875 0.991272 0.130131 +vn -0.065645 0.907376 0.415082 +vn -0.139866 0.439619 0.887204 +vn -0.149937 -0.268838 0.951415 +vn -0.125706 -0.597674 0.791803 +vn -0.114414 -0.665914 0.737175 +vn -0.121586 -0.619068 0.775842 +vn -0.143284 -0.393078 0.908261 +vn -0.153996 0.182318 0.971099 +vn -0.105655 0.737480 0.667043 +vn -0.040986 0.991272 0.125065 +vn -0.128971 0.908322 0.397839 +vn -0.277444 0.439985 0.854030 +vn -0.297678 -0.270821 0.915403 +vn -0.247017 -0.596423 0.763695 +vn -0.230293 -0.666677 0.708853 +vn -0.242897 -0.618915 0.746940 +vn -0.284860 -0.392315 0.874569 +vn -0.305155 0.183966 0.934355 +vn -0.209235 0.737785 0.641743 +vn -0.059999 0.991302 0.116916 +vn -0.190649 0.907834 0.373424 +vn -0.408551 0.438459 0.800501 +vn -0.437391 -0.269601 0.857875 +vn -0.363994 -0.598010 0.714011 +vn -0.340068 -0.667409 0.662465 +vn -0.356914 -0.619648 0.698996 +vn -0.418378 -0.393048 0.818812 +vn -0.447890 0.183874 0.874966 +vn -0.307108 0.738121 0.600665 +vn -0.077486 0.991333 0.105960 +vn -0.246529 0.908017 0.338664 +vn -0.528001 0.440535 0.726005 +vn -0.566179 -0.269875 0.778832 +vn -0.471023 -0.598468 0.648000 +vn -0.437757 -0.667745 0.602008 +vn -0.461531 -0.620136 0.634327 +vn -0.540147 -0.395032 0.743065 +vn -0.578845 0.183844 0.794397 +vn -0.397443 0.737114 0.546495 +vn -0.093051 0.991333 0.092502 +vn -0.296304 0.908078 0.295907 +vn -0.634846 0.440626 0.634632 +vn -0.680776 -0.269967 0.680898 +vn -0.566301 -0.598621 0.566485 +vn -0.526200 -0.667898 0.526261 +vn -0.554674 -0.620319 0.554552 +vn -0.649159 -0.395215 0.649892 +vn -0.695242 0.183874 0.694815 +vn -0.476638 0.738456 0.476943 +vn -0.106388 0.991333 0.076876 +vn -0.338969 0.908017 0.246101 +vn -0.726157 0.440535 0.527787 +vn -0.778741 -0.269875 0.566301 +vn -0.647877 -0.598468 0.471236 +vn -0.601978 -0.667745 0.437819 +vn -0.634419 -0.620136 0.461379 +vn -0.742454 -0.395032 0.540971 +vn -0.794733 0.183905 0.578356 +vn -0.544969 0.738365 0.397198 +vn -0.117252 0.991302 0.059328 +vn -0.373669 0.907834 0.190161 +vn -0.799921 0.440321 0.407697 +vn -0.857814 -0.269601 0.437513 +vn -0.713920 -0.598010 0.364208 +vn -0.663411 -0.667318 0.338450 +vn -0.698019 -0.620472 0.357372 +vn -0.818018 -0.394513 0.418531 +vn -0.875210 0.183966 0.447340 +vn -0.600452 0.738151 0.307474 +vn -0.125309 0.991272 0.040254 +vn -0.397748 0.908322 0.129215 +vn -0.853206 0.441755 0.277230 +vn -0.916257 -0.267525 0.298044 +vn -0.762688 -0.597369 0.247810 +vn -0.708335 -0.666585 0.232154 +vn -0.745994 -0.619770 0.243599 +vn -0.874569 -0.392346 0.284829 +vn -0.934751 0.182165 0.304971 +vn -0.641591 0.737815 0.209632 +vn -0.128544 0.991485 0.020234 +vn -0.415174 0.907376 0.065127 +vn -0.888089 0.437910 0.139622 +vn -0.951018 -0.270455 0.149541 +vn -0.792077 -0.597644 0.124058 +vn -0.736778 -0.666036 0.116245 +vn -0.776360 -0.618213 0.122623 +vn -0.909452 -0.390301 0.143315 +vn -0.970794 0.184149 0.153691 +vn -0.665792 0.738609 0.105411 +vn -0.131840 0.991241 -0.000366 +vn -0.422132 0.906522 -0.000397 +vn -0.898099 0.439741 -0.000122 +vn -0.963195 -0.268715 0.000061 +vn -0.802637 -0.596423 0.000092 +vn -0.746178 -0.665700 -0.000031 +vn -0.785577 -0.618732 0.000916 +vn -0.920194 -0.391369 0.000000 +vn -0.983215 0.182409 0.000031 +vn -0.675436 0.737388 0.000183 +vn -0.130131 0.991272 -0.020875 +vn -0.415082 0.907376 -0.065645 +vn -0.886349 0.441450 -0.139500 +vn -0.951415 -0.268838 -0.149937 +vn -0.792627 -0.596728 -0.124882 +vn -0.737175 -0.665914 -0.114414 +vn -0.775842 -0.619037 -0.121586 +vn -0.908780 -0.391644 -0.143864 +vn -0.971099 0.182257 -0.154057 +vn -0.667043 0.737480 -0.105655 +vn -0.125065 0.991272 -0.040986 +vn -0.397839 0.908322 -0.128971 +vn -0.853328 0.441725 -0.276833 +vn -0.915983 -0.269173 -0.297433 +vn -0.762749 -0.597369 -0.247597 +vn -0.709525 -0.666555 -0.228523 +vn -0.746940 -0.618885 -0.242927 +vn -0.874569 -0.392315 -0.284860 +vn -0.934355 0.183996 -0.305124 +vn -0.640400 0.738945 -0.209296 +vn -0.116916 0.991302 -0.059999 +vn -0.373424 0.907834 -0.190649 +vn -0.799890 0.440199 -0.407849 +vn -0.857265 -0.271218 -0.437605 +vn -0.712973 -0.598956 -0.364513 +vn -0.663411 -0.667318 -0.338420 +vn -0.699088 -0.619587 -0.356822 +vn -0.818781 -0.393078 -0.418348 +vn -0.874966 0.183874 -0.447890 +vn -0.600665 0.738121 -0.307108 +vn -0.105960 0.991333 -0.077486 +vn -0.338664 0.908017 -0.246529 +vn -0.726005 0.440535 -0.528001 +vn -0.778161 -0.271493 -0.566301 +vn -0.648000 -0.598468 -0.471023 +vn -0.602008 -0.667745 -0.437757 +vn -0.634327 -0.620136 -0.461562 +vn -0.743370 -0.392163 -0.541795 +vn -0.794397 0.183844 -0.578875 +vn -0.546495 0.737114 -0.397443 +vn -0.092502 0.991333 -0.093051 +vn -0.295083 0.908780 -0.294900 +vn -0.634632 0.440626 -0.634846 +vn -0.680898 -0.269967 -0.680776 +vn -0.566485 -0.598621 -0.566301 +vn -0.526261 -0.667898 -0.526200 +vn -0.554552 -0.620319 -0.554674 +vn -0.649892 -0.395215 -0.649159 +vn -0.695303 0.182043 -0.695242 +vn -0.476943 0.738456 -0.476638 +vn -0.076876 0.991333 -0.106388 +vn -0.246101 0.908017 -0.339000 +vn -0.528153 0.438704 -0.727012 +vn -0.566301 -0.269875 -0.778741 +vn -0.471236 -0.598468 -0.647877 +vn -0.437819 -0.667745 -0.601978 +vn -0.461867 -0.620960 -0.633290 +vn -0.541002 -0.395032 -0.742454 +vn -0.578356 0.183905 -0.794763 +vn -0.397198 0.738365 -0.544969 +vn -0.059328 0.991302 -0.117252 +vn -0.190161 0.907834 -0.373669 +vn -0.407697 0.440321 -0.799921 +vn -0.437513 -0.269601 -0.857814 +vn -0.364208 -0.598010 -0.713920 +vn -0.338450 -0.667318 -0.663411 +vn -0.357341 -0.620472 -0.698050 +vn -0.418348 -0.393048 -0.818812 +vn -0.447340 0.183996 -0.875210 +vn -0.307474 0.738151 -0.600452 +vn -0.040254 0.991272 -0.125309 +vn -0.129215 0.908322 -0.397748 +vn -0.277230 0.441755 -0.853206 +vn -0.297525 -0.269204 -0.915952 +vn -0.248817 -0.596393 -0.763115 +vn -0.230323 -0.666677 -0.708853 +vn -0.243599 -0.619770 -0.745994 +vn -0.284829 -0.392376 -0.874569 +vn -0.304544 0.184027 -0.934507 +vn -0.209632 0.737815 -0.641591 +vn -0.020112 0.991272 -0.130253 +vn -0.065127 0.907376 -0.415174 +vn -0.139622 0.437910 -0.888089 +vn -0.149541 -0.270455 -0.951018 +vn -0.125095 -0.596728 -0.792596 +vn -0.114353 -0.666097 -0.737022 +vn -0.123447 -0.619068 -0.775536 +vn -0.143315 -0.390301 -0.909452 +vn -0.153691 0.184118 -0.970794 +vn -0.106082 0.737480 -0.666951 +vn 0.000366 0.991241 -0.131840 +vn 0.000244 0.907346 -0.420362 +vn 0.000122 0.439741 -0.898099 +vn -0.000061 -0.268715 -0.963195 +vn -0.000092 -0.596423 -0.802637 +vn 0.000031 -0.665700 -0.746178 +vn 0.000092 -0.617939 -0.786218 +vn 0.000610 -0.389996 -0.920804 +vn -0.000336 0.180548 -0.983551 +vn -0.000183 0.737388 -0.675436 +vn 0.020875 0.991272 -0.130131 +vn 0.065645 0.907376 -0.415082 +vn 0.139988 0.439711 -0.887143 +vn 0.149937 -0.268838 -0.951415 +vn 0.124882 -0.596728 -0.792627 +vn 0.116276 -0.666036 -0.736778 +vn 0.121586 -0.619068 -0.775842 +vn 0.143254 -0.393109 -0.908231 +vn 0.153996 0.182318 -0.971099 +vn 0.105930 0.738609 -0.665731 +vn 0.040986 0.991272 -0.125065 +vn 0.129826 0.907590 -0.399182 +vn 0.276833 0.441725 -0.853359 +vn 0.297372 -0.269204 -0.915983 +vn 0.247597 -0.597369 -0.762749 +vn 0.228523 -0.666555 -0.709525 +vn 0.241768 -0.619739 -0.746605 +vn 0.284860 -0.392346 -0.874569 +vn 0.305155 0.183996 -0.934355 +vn 0.209418 0.736534 -0.643117 +vn 0.059999 0.991302 -0.116916 +vn 0.190649 0.907834 -0.373424 +vn 0.407270 0.442030 -0.799188 +vn 0.437605 -0.271218 -0.857265 +vn 0.363567 -0.597034 -0.715049 +vn 0.338420 -0.667318 -0.663411 +vn 0.356822 -0.619587 -0.699088 +vn 0.418348 -0.393078 -0.818781 +vn 0.447890 0.183905 -0.874935 +vn 0.307108 0.738121 -0.600665 +vn 0.077486 0.991333 -0.105960 +vn 0.246529 0.908017 -0.338664 +vn 0.528031 0.440535 -0.726005 +vn 0.566301 -0.271493 -0.778161 +vn 0.471023 -0.598468 -0.648000 +vn 0.439253 -0.667837 -0.600818 +vn 0.461562 -0.620136 -0.634327 +vn 0.541795 -0.392163 -0.743370 +vn 0.578875 0.183874 -0.794397 +vn 0.397443 0.737114 -0.546495 +vn 0.093051 0.991333 -0.092502 +vn 0.296304 0.908078 -0.295907 +vn 0.634846 0.440626 -0.634632 +vn 0.680776 -0.269967 -0.680898 +vn 0.566301 -0.598621 -0.566485 +vn 0.526200 -0.667898 -0.526261 +vn 0.554674 -0.620319 -0.554552 +vn 0.650014 -0.393811 -0.649892 +vn 0.695242 0.180181 -0.695791 +vn 0.476638 0.738456 -0.476943 +vn 0.106388 0.991333 -0.076876 +vn 0.339000 0.908017 -0.246101 +vn 0.726157 0.440535 -0.527787 +vn 0.778741 -0.269875 -0.566301 +vn 0.647877 -0.598468 -0.471236 +vn 0.601978 -0.667745 -0.437819 +vn 0.633290 -0.620960 -0.461867 +vn 0.742454 -0.395032 -0.541002 +vn 0.794794 0.182073 -0.578875 +vn 0.544237 0.739525 -0.396069 +vn 0.117252 0.991302 -0.059328 +vn 0.373669 0.907834 -0.190161 +vn 0.799921 0.440321 -0.407697 +vn 0.857814 -0.269570 -0.437513 +vn 0.714194 -0.597034 -0.365276 +vn 0.663411 -0.667318 -0.338450 +vn 0.698050 -0.620472 -0.357372 +vn 0.818812 -0.393048 -0.418348 +vn 0.875362 0.182134 -0.447768 +vn 0.600452 0.738151 -0.307474 +vn 0.125309 0.991272 -0.040254 +vn 0.399365 0.907620 -0.129307 +vn 0.854122 0.440016 -0.277139 +vn 0.915952 -0.269204 -0.297525 +vn 0.762688 -0.597369 -0.247810 +vn 0.709372 -0.666738 -0.228492 +vn 0.745994 -0.619770 -0.243568 +vn 0.874569 -0.392376 -0.284829 +vn 0.934538 0.184057 -0.304544 +vn 0.640553 0.738945 -0.208808 +vn 0.130253 0.991272 -0.020112 +vn 0.413556 0.908109 -0.065279 +vn 0.887173 0.439741 -0.139683 +vn 0.951415 -0.268838 -0.150090 +vn 0.792596 -0.596728 -0.125095 +vn 0.736778 -0.666036 -0.116245 +vn 0.776330 -0.618275 -0.122501 +vn 0.909421 -0.390332 -0.143315 +vn 0.970794 0.184149 -0.153691 +vn 0.666951 0.737480 -0.106082 +vn 0.373669 0.925657 0.059053 +vn 0.378460 0.925596 -0.000214 +vn 0.245491 0.968596 0.038728 +vn 0.248665 0.968566 -0.000183 +vn 0.183111 0.982665 0.028871 +vn 0.185461 0.982635 -0.000153 +vn 0.154698 0.987640 0.024445 +vn 0.156713 0.987610 -0.000092 +vn 0.149968 0.988372 0.023835 +vn 0.151952 0.988372 0.000031 +vn 0.168889 0.985260 0.026948 +vn 0.171148 0.985229 0.000153 +vn 0.222266 0.974334 0.035463 +vn 0.225196 0.974303 0.000183 +vn 0.348674 0.935575 0.055574 +vn 0.353252 0.935514 0.000214 +vn 0.669454 0.735130 0.106479 +vn 0.677999 0.735038 0.000214 +vn 0.838862 0.528703 0.129460 +vn 0.848903 0.528489 -0.003632 +vn 0.357830 0.926389 0.117161 +vn 0.236061 0.968688 0.076815 +vn 0.176061 0.982696 0.057253 +vn 0.148747 0.987671 0.048433 +vn 0.144169 0.988403 0.047090 +vn 0.162358 0.985290 0.053133 +vn 0.213630 0.974395 0.069948 +vn 0.335215 0.935728 0.109653 +vn 0.643971 0.735496 0.210425 +vn 0.807733 0.529191 0.259804 +vn 0.336161 0.925993 0.171728 +vn 0.220832 0.968749 0.112735 +vn 0.164708 0.982727 0.084078 +vn 0.139164 0.987701 0.071078 +vn 0.134831 0.988433 0.069033 +vn 0.151830 0.985321 0.077853 +vn 0.199805 0.974456 0.102451 +vn 0.313547 0.935850 0.160680 +vn 0.602649 0.735862 0.308634 +vn 0.756737 0.529618 0.383129 +vn 0.305063 0.926084 0.221900 +vn 0.200415 0.968810 0.145695 +vn 0.149480 0.982757 0.108646 +vn 0.126286 0.987732 0.091830 +vn 0.122318 0.988464 0.089145 +vn 0.137730 0.985351 0.100497 +vn 0.181249 0.974487 0.132267 +vn 0.284433 0.935942 0.207465 +vn 0.546922 0.736137 0.398633 +vn 0.687613 0.529923 0.496323 +vn 0.265450 0.926695 0.265969 +vn 0.175298 0.968810 0.174993 +vn 0.130741 0.982757 0.130497 +vn 0.110446 0.987732 0.110294 +vn 0.106967 0.988464 0.107028 +vn 0.120426 0.985351 0.120640 +vn 0.158452 0.974487 0.158757 +vn 0.248115 0.936521 0.247658 +vn 0.478317 0.736229 0.478652 +vn 0.602130 0.530076 0.597003 +vn 0.222266 0.926084 0.304788 +vn 0.146031 0.968810 0.200140 +vn 0.110202 0.982543 0.149785 +vn 0.092013 0.987732 0.126164 +vn 0.090030 0.988525 0.121220 +vn 0.100253 0.985351 0.137913 +vn 0.131932 0.974487 0.181494 +vn 0.206702 0.936491 0.283242 +vn 0.398938 0.734855 0.548448 +vn 0.502213 0.529954 0.683309 +vn 0.172155 0.925993 0.335948 +vn 0.113132 0.968749 0.220649 +vn 0.084384 0.982727 0.164556 +vn 0.069887 0.987793 0.138981 +vn 0.068941 0.988433 0.134892 +vn 0.077334 0.985137 0.153325 +vn 0.102084 0.974456 0.199988 +vn 0.160253 0.935850 0.313761 +vn 0.308664 0.734581 0.604205 +vn 0.389630 0.529649 0.753410 +vn 0.117466 0.925840 0.359172 +vn 0.077212 0.968688 0.235908 +vn 0.057588 0.982696 0.175939 +vn 0.048616 0.987671 0.148686 +vn 0.046999 0.988403 0.144200 +vn 0.052828 0.985290 0.162450 +vn 0.069521 0.974395 0.213782 +vn 0.109195 0.935728 0.335368 +vn 0.209998 0.735496 0.644124 +vn 0.266762 0.529221 0.805444 +vn 0.059511 0.925657 0.373577 +vn 0.039155 0.968596 0.245430 +vn 0.029206 0.982665 0.183050 +vn 0.024659 0.987640 0.154668 +vn 0.023743 0.988372 0.149968 +vn 0.026643 0.985260 0.168950 +vn 0.035066 0.974334 0.222327 +vn 0.055086 0.935575 0.348766 +vn 0.106052 0.735130 0.669546 +vn 0.136692 0.528733 0.837703 +vn 0.000610 0.924986 0.379986 +vn 0.000183 0.968566 0.248665 +vn 0.000153 0.982635 0.185461 +vn 0.000092 0.987610 0.156713 +vn -0.000031 0.988372 0.151952 +vn -0.000153 0.985229 0.171148 +vn -0.000183 0.974303 0.225196 +vn -0.000214 0.935514 0.353252 +vn -0.000214 0.735038 0.677999 +vn 0.003632 0.528489 0.848903 +vn -0.059053 0.925657 0.373669 +vn -0.039247 0.968932 0.244118 +vn -0.028871 0.982665 0.183111 +vn -0.024445 0.987640 0.154698 +vn -0.023835 0.988372 0.149968 +vn -0.026948 0.985260 0.168889 +vn -0.035463 0.974334 0.222266 +vn -0.055574 0.935575 0.348674 +vn -0.106479 0.735130 0.669454 +vn -0.129460 0.528703 0.838862 +vn -0.117008 0.925840 0.359325 +vn -0.076815 0.968688 0.236061 +vn -0.057253 0.982696 0.176061 +vn -0.048433 0.987671 0.148747 +vn -0.047090 0.988403 0.144169 +vn -0.053133 0.985290 0.162358 +vn -0.069948 0.974395 0.213630 +vn -0.109653 0.935728 0.335215 +vn -0.209632 0.736686 0.642903 +vn -0.259804 0.529191 0.807733 +vn -0.171728 0.925993 0.336161 +vn -0.112735 0.968749 0.220832 +vn -0.084078 0.982727 0.164708 +vn -0.071078 0.987701 0.139164 +vn -0.069033 0.988433 0.134831 +vn -0.077853 0.985321 0.151830 +vn -0.102451 0.974456 0.199805 +vn -0.160680 0.935850 0.313547 +vn -0.308634 0.735893 0.602649 +vn -0.386242 0.529679 0.755120 +vn -0.221900 0.926084 0.305063 +vn -0.145695 0.968810 0.200415 +vn -0.108646 0.982757 0.149480 +vn -0.091830 0.987732 0.126286 +vn -0.089145 0.988464 0.122318 +vn -0.100497 0.985351 0.137730 +vn -0.132267 0.974487 0.181249 +vn -0.207465 0.935942 0.284433 +vn -0.398633 0.736137 0.546922 +vn -0.496292 0.529923 0.687613 +vn -0.265969 0.926695 0.265450 +vn -0.174993 0.968810 0.175298 +vn -0.130497 0.982757 0.130741 +vn -0.110294 0.987732 0.110446 +vn -0.107028 0.988464 0.106967 +vn -0.120640 0.985351 0.120426 +vn -0.158757 0.974487 0.158452 +vn -0.249062 0.935972 0.248726 +vn -0.478652 0.736229 0.478317 +vn -0.597003 0.530076 0.602130 +vn -0.304788 0.926084 0.222266 +vn -0.200140 0.968810 0.146031 +vn -0.149266 0.982757 0.108921 +vn -0.126164 0.987732 0.092013 +vn -0.122379 0.988464 0.089084 +vn -0.137913 0.985351 0.100253 +vn -0.181494 0.974487 0.131932 +vn -0.283242 0.936491 0.206702 +vn -0.547197 0.736137 0.398297 +vn -0.683309 0.529954 0.502213 +vn -0.335948 0.925993 0.172155 +vn -0.220649 0.968749 0.113132 +vn -0.164556 0.982727 0.084384 +vn -0.139042 0.987701 0.071261 +vn -0.134892 0.988433 0.068941 +vn -0.151982 0.985321 0.077578 +vn -0.199988 0.974456 0.102084 +vn -0.313761 0.935850 0.160253 +vn -0.604205 0.734581 0.308664 +vn -0.753410 0.529649 0.389630 +vn -0.359172 0.925840 0.117466 +vn -0.235908 0.968688 0.077212 +vn -0.175939 0.982696 0.057588 +vn -0.148686 0.987671 0.048616 +vn -0.144200 0.988403 0.046999 +vn -0.161199 0.985443 0.053438 +vn -0.212348 0.974670 0.069826 +vn -0.335368 0.935728 0.109195 +vn -0.644124 0.735496 0.209998 +vn -0.805444 0.529221 0.266762 +vn -0.373577 0.925657 0.059511 +vn -0.245430 0.968596 0.039155 +vn -0.183050 0.982665 0.029206 +vn -0.154668 0.987640 0.024659 +vn -0.149968 0.988372 0.023743 +vn -0.168950 0.985260 0.026643 +vn -0.223792 0.973998 0.034730 +vn -0.350352 0.934965 0.054994 +vn -0.669546 0.735130 0.106052 +vn -0.837703 0.528733 0.136692 +vn -0.378460 0.925596 0.000214 +vn -0.248665 0.968566 0.000183 +vn -0.185461 0.982635 0.000153 +vn -0.156713 0.987610 0.000092 +vn -0.151952 0.988372 -0.000031 +vn -0.171148 0.985229 -0.000153 +vn -0.225196 0.974303 -0.000183 +vn -0.351817 0.936033 0.000336 +vn -0.677999 0.735038 -0.000214 +vn -0.848903 0.528489 0.003632 +vn -0.373669 0.925657 -0.059053 +vn -0.245491 0.968596 -0.038728 +vn -0.183111 0.982665 -0.028871 +vn -0.154698 0.987640 -0.024445 +vn -0.149968 0.988372 -0.023835 +vn -0.168889 0.985260 -0.026948 +vn -0.222266 0.974334 -0.035463 +vn -0.348674 0.935575 -0.055574 +vn -0.670797 0.733848 -0.106937 +vn -0.838862 0.528703 -0.129460 +vn -0.359325 0.925840 -0.117008 +vn -0.236061 0.968688 -0.076815 +vn -0.176061 0.982696 -0.057253 +vn -0.148747 0.987671 -0.048433 +vn -0.144169 0.988403 -0.047090 +vn -0.162358 0.985290 -0.053133 +vn -0.213630 0.974395 -0.069948 +vn -0.335215 0.935728 -0.109653 +vn -0.643971 0.735496 -0.210425 +vn -0.807733 0.529191 -0.259804 +vn -0.336161 0.925993 -0.171728 +vn -0.220832 0.968749 -0.112735 +vn -0.164708 0.982727 -0.084078 +vn -0.139164 0.987701 -0.071078 +vn -0.134831 0.988433 -0.069033 +vn -0.151830 0.985321 -0.077853 +vn -0.199805 0.974456 -0.102451 +vn -0.313547 0.935850 -0.160680 +vn -0.602649 0.735862 -0.308634 +vn -0.756737 0.529618 -0.383129 +vn -0.305063 0.926084 -0.221900 +vn -0.200415 0.968810 -0.145695 +vn -0.149480 0.982757 -0.108646 +vn -0.126286 0.987732 -0.091830 +vn -0.122318 0.988464 -0.089145 +vn -0.137730 0.985351 -0.100497 +vn -0.181249 0.974487 -0.132267 +vn -0.284433 0.935942 -0.207465 +vn -0.546922 0.736137 -0.398633 +vn -0.687613 0.529923 -0.496323 +vn -0.265450 0.926695 -0.265969 +vn -0.175298 0.968810 -0.174993 +vn -0.130741 0.982757 -0.130497 +vn -0.110446 0.987732 -0.110294 +vn -0.106967 0.988464 -0.107028 +vn -0.120426 0.985351 -0.120640 +vn -0.158452 0.974487 -0.158757 +vn -0.248115 0.936521 -0.247658 +vn -0.477737 0.737388 -0.477432 +vn -0.602161 0.530045 -0.597003 +vn -0.222266 0.926084 -0.304788 +vn -0.147282 0.968444 -0.200995 +vn -0.108921 0.982757 -0.149266 +vn -0.092013 0.987732 -0.126164 +vn -0.089084 0.988464 -0.122379 +vn -0.100253 0.985351 -0.137913 +vn -0.131932 0.974487 -0.181494 +vn -0.206702 0.936491 -0.283242 +vn -0.398938 0.734855 -0.548448 +vn -0.499374 0.529923 -0.685385 +vn -0.172155 0.925993 -0.335948 +vn -0.113132 0.968749 -0.220649 +vn -0.083102 0.982940 -0.163976 +vn -0.071261 0.987701 -0.139073 +vn -0.068941 0.988433 -0.134892 +vn -0.077578 0.985321 -0.151982 +vn -0.102084 0.974456 -0.199988 +vn -0.160253 0.935850 -0.313761 +vn -0.308664 0.734581 -0.604205 +vn -0.389630 0.529649 -0.753410 +vn -0.117466 0.925840 -0.359172 +vn -0.077212 0.968688 -0.235908 +vn -0.057588 0.982696 -0.175939 +vn -0.048616 0.987671 -0.148686 +vn -0.046999 0.988403 -0.144200 +vn -0.053438 0.985443 -0.161199 +vn -0.069521 0.974395 -0.213782 +vn -0.109195 0.935728 -0.335368 +vn -0.209998 0.735496 -0.644124 +vn -0.266762 0.529221 -0.805444 +vn -0.060152 0.925047 -0.375011 +vn -0.039155 0.968596 -0.245430 +vn -0.029206 0.982665 -0.183050 +vn -0.024659 0.987640 -0.154668 +vn -0.023743 0.988372 -0.149968 +vn -0.026643 0.985260 -0.168950 +vn -0.034730 0.973998 -0.223792 +vn -0.055086 0.935575 -0.348766 +vn -0.106052 0.735130 -0.669546 +vn -0.136692 0.528733 -0.837703 +vn -0.000214 0.925596 -0.378460 +vn 0.000488 0.968902 -0.247383 +vn -0.000153 0.982635 -0.185461 +vn -0.000092 0.987610 -0.156713 +vn 0.000031 0.988372 -0.151952 +vn 0.000153 0.985229 -0.171148 +vn 0.000183 0.974303 -0.225196 +vn 0.000214 0.935514 -0.353252 +vn 0.000214 0.735038 -0.677999 +vn -0.003632 0.528489 -0.848903 +vn 0.059053 0.925657 -0.373638 +vn 0.038728 0.968596 -0.245491 +vn 0.028871 0.982665 -0.183111 +vn 0.024445 0.987640 -0.154698 +vn 0.023835 0.988372 -0.149968 +vn 0.026948 0.985260 -0.168889 +vn 0.035463 0.974334 -0.222266 +vn 0.055574 0.935575 -0.348674 +vn 0.106479 0.735130 -0.669454 +vn 0.129460 0.528703 -0.838862 +vn 0.117008 0.925840 -0.359325 +vn 0.076815 0.968688 -0.236061 +vn 0.057253 0.982696 -0.176061 +vn 0.048433 0.987671 -0.148747 +vn 0.047090 0.988403 -0.144169 +vn 0.053133 0.985290 -0.162358 +vn 0.069948 0.974395 -0.213630 +vn 0.109653 0.935728 -0.335215 +vn 0.210425 0.735496 -0.643971 +vn 0.259804 0.529191 -0.807733 +vn 0.171667 0.926542 -0.334666 +vn 0.112735 0.968749 -0.220832 +vn 0.084078 0.982727 -0.164708 +vn 0.071078 0.987701 -0.139164 +vn 0.069033 0.988433 -0.134831 +vn 0.077853 0.985321 -0.151830 +vn 0.102451 0.974456 -0.199805 +vn 0.160680 0.935850 -0.313547 +vn 0.308634 0.735862 -0.602649 +vn 0.383129 0.529618 -0.756737 +vn 0.221900 0.926084 -0.305063 +vn 0.145695 0.968810 -0.200415 +vn 0.108646 0.982757 -0.149480 +vn 0.091830 0.987732 -0.126286 +vn 0.089145 0.988464 -0.122318 +vn 0.100497 0.985351 -0.137730 +vn 0.132267 0.974487 -0.181249 +vn 0.206153 0.936491 -0.283639 +vn 0.397534 0.737297 -0.546159 +vn 0.496323 0.529923 -0.687613 +vn 0.265969 0.926695 -0.265450 +vn 0.174993 0.968810 -0.175298 +vn 0.130497 0.982757 -0.130741 +vn 0.110294 0.987732 -0.110446 +vn 0.107028 0.988464 -0.106967 +vn 0.120640 0.985351 -0.120426 +vn 0.157353 0.974792 -0.158086 +vn 0.249062 0.936003 -0.248726 +vn 0.479781 0.734947 -0.479171 +vn 0.599475 0.530045 -0.599689 +vn 0.304788 0.926084 -0.222266 +vn 0.200140 0.968810 -0.146031 +vn 0.149785 0.982543 -0.110202 +vn 0.126164 0.987732 -0.092013 +vn 0.122379 0.988464 -0.089084 +vn 0.137913 0.985351 -0.100253 +vn 0.181494 0.974487 -0.131932 +vn 0.284738 0.935881 -0.207343 +vn 0.547197 0.736137 -0.398297 +vn 0.683309 0.529923 -0.502213 +vn 0.335948 0.925993 -0.172155 +vn 0.220649 0.968749 -0.113132 +vn 0.164556 0.982727 -0.084384 +vn 0.138981 0.987793 -0.069887 +vn 0.134892 0.988433 -0.068941 +vn 0.151982 0.985321 -0.077578 +vn 0.199988 0.974456 -0.102084 +vn 0.313761 0.935850 -0.160253 +vn 0.604205 0.734581 -0.308664 +vn 0.753410 0.529649 -0.389630 +vn 0.359172 0.925840 -0.117466 +vn 0.235908 0.968688 -0.077212 +vn 0.175939 0.982696 -0.057588 +vn 0.148686 0.987671 -0.048616 +vn 0.144200 0.988403 -0.046999 +vn 0.162450 0.985290 -0.052828 +vn 0.212348 0.974670 -0.069826 +vn 0.335368 0.935728 -0.109195 +vn 0.644124 0.735496 -0.209998 +vn 0.805444 0.529221 -0.266762 +vn 0.373577 0.925657 -0.059511 +vn 0.245430 0.968596 -0.039155 +vn 0.183050 0.982665 -0.029206 +vn 0.154668 0.987640 -0.024659 +vn 0.149968 0.988372 -0.023743 +vn 0.168950 0.985260 -0.026643 +vn 0.222327 0.974334 -0.035066 +vn 0.350352 0.934965 -0.054994 +vn 0.669546 0.735130 -0.106052 +vn 0.837703 0.528733 -0.136692 +usemtl m1 +s 1 +f 1/1/1 2/2/2 3/3/3 +f 4/4/4 1/1/1 3/3/3 +f 4/4/4 3/3/3 5/5/5 +f 5/5/5 6/6/6 4/4/4 +f 6/6/6 5/5/5 7/7/7 +f 7/7/7 8/8/8 6/6/6 +f 8/8/8 7/7/7 9/9/9 +f 9/9/9 10/10/10 8/8/8 +f 10/10/10 9/9/9 11/11/11 +f 11/11/11 12/12/12 10/10/10 +f 12/12/12 11/11/11 13/13/13 +f 13/13/13 14/14/14 12/12/12 +f 14/14/14 13/13/13 15/15/15 +f 15/15/15 16/16/16 14/14/14 +f 16/16/16 15/15/15 17/17/17 +f 17/17/17 18/18/18 16/16/16 +f 18/18/18 17/17/17 19/19/19 +f 19/19/19 20/20/20 18/18/18 +f 20/20/20 19/19/19 21/21/21 +f 21/21/21 22/22/22 20/20/20 +f 2/2/2 23/23/23 24/24/24 +f 24/24/24 3/3/3 2/2/2 +f 3/3/3 24/24/24 25/25/25 +f 25/25/25 5/5/5 3/3/3 +f 5/5/5 25/25/25 26/26/26 +f 26/26/26 7/7/7 5/5/5 +f 7/7/7 26/26/26 27/27/27 +f 27/27/27 9/9/9 7/7/7 +f 9/9/9 27/27/27 28/28/28 +f 28/28/28 11/11/11 9/9/9 +f 11/11/11 28/28/28 29/29/29 +f 29/29/29 13/13/13 11/11/11 +f 13/13/13 29/29/29 30/30/30 +f 30/30/30 15/15/15 13/13/13 +f 15/15/15 30/30/30 31/31/31 +f 31/31/31 17/17/17 15/15/15 +f 17/17/17 31/31/31 32/32/32 +f 32/32/32 19/19/19 17/17/17 +f 19/19/19 32/32/32 33/33/33 +f 33/33/33 21/21/21 19/19/19 +f 23/23/23 34/34/34 35/35/35 +f 35/35/35 24/24/24 23/23/23 +f 24/24/24 35/35/35 36/36/36 +f 36/36/36 25/25/25 24/24/24 +f 25/25/25 36/36/36 37/37/37 +f 37/37/37 26/26/26 25/25/25 +f 26/26/26 37/37/37 38/38/38 +f 38/38/38 27/27/27 26/26/26 +f 27/27/27 38/38/38 39/39/39 +f 39/39/39 28/28/28 27/27/27 +f 28/28/28 39/39/39 40/40/40 +f 40/40/40 29/29/29 28/28/28 +f 29/29/29 40/40/40 41/41/41 +f 41/41/41 30/30/30 29/29/29 +f 30/30/30 41/41/41 42/42/42 +f 42/42/42 31/31/31 30/30/30 +f 31/31/31 42/42/42 43/43/43 +f 43/43/43 32/32/32 31/31/31 +f 32/32/32 43/43/43 44/44/44 +f 44/44/44 33/33/33 32/32/32 +f 34/34/34 45/45/45 46/46/46 +f 46/46/46 35/35/35 34/34/34 +f 35/35/35 46/46/46 47/47/47 +f 47/47/47 36/36/36 35/35/35 +f 36/36/36 47/47/47 48/48/48 +f 48/48/48 37/37/37 36/36/36 +f 37/37/37 48/48/48 49/49/49 +f 49/49/49 38/38/38 37/37/37 +f 38/38/38 49/49/49 50/50/50 +f 50/50/50 39/39/39 38/38/38 +f 39/39/39 50/50/50 51/51/51 +f 51/51/51 40/40/40 39/39/39 +f 40/40/40 51/51/51 52/52/52 +f 52/52/52 41/41/41 40/40/40 +f 41/41/41 52/52/52 53/53/53 +f 53/53/53 42/42/42 41/41/41 +f 42/42/42 53/53/53 54/54/54 +f 54/54/54 43/43/43 42/42/42 +f 43/43/43 54/54/54 55/55/55 +f 55/55/55 44/44/44 43/43/43 +f 45/45/45 56/56/56 57/57/57 +f 57/57/57 46/46/46 45/45/45 +f 46/46/46 57/57/57 58/58/58 +f 58/58/58 47/47/47 46/46/46 +f 47/47/47 58/58/58 59/59/59 +f 59/59/59 48/48/48 47/47/47 +f 48/48/48 59/59/59 60/60/60 +f 60/60/60 49/49/49 48/48/48 +f 49/49/49 60/60/60 61/61/61 +f 61/61/61 50/50/50 49/49/49 +f 50/50/50 61/61/61 62/62/62 +f 62/62/62 51/51/51 50/50/50 +f 51/51/51 62/62/62 63/63/63 +f 63/63/63 52/52/52 51/51/51 +f 52/52/52 63/63/63 64/64/64 +f 64/64/64 53/53/53 52/52/52 +f 53/53/53 64/64/64 65/65/65 +f 65/65/65 54/54/54 53/53/53 +f 54/54/54 65/65/65 66/66/66 +f 66/66/66 55/55/55 54/54/54 +f 56/56/56 67/67/67 68/68/68 +f 68/68/68 57/57/57 56/56/56 +f 57/57/57 68/68/68 69/69/69 +f 69/69/69 58/58/58 57/57/57 +f 58/58/58 69/69/69 70/70/70 +f 70/70/70 59/59/59 58/58/58 +f 59/59/59 70/70/70 71/71/71 +f 71/71/71 60/60/60 59/59/59 +f 60/60/60 71/71/71 72/72/72 +f 72/72/72 61/61/61 60/60/60 +f 61/61/61 72/72/72 73/73/73 +f 73/73/73 62/62/62 61/61/61 +f 62/62/62 73/73/73 74/74/74 +f 74/74/74 63/63/63 62/62/62 +f 63/63/63 74/74/74 75/75/75 +f 75/75/75 64/64/64 63/63/63 +f 64/64/64 75/75/75 76/76/76 +f 76/76/76 65/65/65 64/64/64 +f 65/65/65 76/76/76 77/77/77 +f 77/77/77 66/66/66 65/65/65 +f 67/67/67 78/78/78 79/79/79 +f 79/79/79 68/68/68 67/67/67 +f 68/68/68 79/79/79 80/80/80 +f 80/80/80 69/69/69 68/68/68 +f 69/69/69 80/80/80 81/81/81 +f 81/81/81 70/70/70 69/69/69 +f 70/70/70 81/81/81 82/82/82 +f 82/82/82 71/71/71 70/70/70 +f 71/71/71 82/82/82 83/83/83 +f 83/83/83 72/72/72 71/71/71 +f 72/72/72 83/83/83 84/84/84 +f 84/84/84 73/73/73 72/72/72 +f 73/73/73 84/84/84 85/85/85 +f 85/85/85 74/74/74 73/73/73 +f 74/74/74 85/85/85 86/86/86 +f 86/86/86 75/75/75 74/74/74 +f 75/75/75 86/86/86 87/87/87 +f 87/87/87 76/76/76 75/75/75 +f 76/76/76 87/87/87 88/88/88 +f 88/88/88 77/77/77 76/76/76 +f 78/78/78 89/89/89 90/90/90 +f 90/90/90 79/79/79 78/78/78 +f 79/79/79 90/90/90 91/91/91 +f 91/91/91 80/80/80 79/79/79 +f 80/80/80 91/91/91 92/92/92 +f 92/92/92 81/81/81 80/80/80 +f 81/81/81 92/92/92 93/93/93 +f 93/93/93 82/82/82 81/81/81 +f 82/82/82 93/93/93 94/94/94 +f 94/94/94 83/83/83 82/82/82 +f 83/83/83 94/94/94 95/95/95 +f 95/95/95 84/84/84 83/83/83 +f 84/84/84 95/95/95 96/96/96 +f 96/96/96 85/85/85 84/84/84 +f 85/85/85 96/96/96 97/97/97 +f 97/97/97 86/86/86 85/85/85 +f 86/86/86 97/97/97 98/98/98 +f 98/98/98 87/87/87 86/86/86 +f 87/87/87 98/98/98 99/99/99 +f 99/99/99 88/88/88 87/87/87 +f 89/89/89 100/100/100 101/101/101 +f 101/101/101 90/90/90 89/89/89 +f 90/90/90 101/101/101 102/102/102 +f 102/102/102 91/91/91 90/90/90 +f 91/91/91 102/102/102 103/103/103 +f 103/103/103 92/92/92 91/91/91 +f 92/92/92 103/103/103 104/104/104 +f 104/104/104 93/93/93 92/92/92 +f 93/93/93 104/104/104 105/105/105 +f 105/105/105 94/94/94 93/93/93 +f 94/94/94 105/105/105 106/106/106 +f 106/106/106 95/95/95 94/94/94 +f 95/95/95 106/106/106 107/107/107 +f 107/107/107 96/96/96 95/95/95 +f 96/96/96 107/107/107 108/108/108 +f 108/108/108 97/97/97 96/96/96 +f 97/97/97 108/108/108 109/109/109 +f 109/109/109 98/98/98 97/97/97 +f 98/98/98 109/109/109 110/110/110 +f 110/110/110 99/99/99 98/98/98 +f 100/100/100 111/111/111 112/112/112 +f 112/112/112 101/101/101 100/100/100 +f 101/101/101 112/112/112 113/113/113 +f 113/113/113 102/102/102 101/101/101 +f 102/102/102 113/113/113 114/114/114 +f 114/114/114 103/103/103 102/102/102 +f 103/103/103 114/114/114 115/115/115 +f 115/115/115 104/104/104 103/103/103 +f 104/104/104 115/115/115 116/116/116 +f 116/116/116 105/105/105 104/104/104 +f 105/105/105 116/116/116 117/117/117 +f 117/117/117 106/106/106 105/105/105 +f 106/106/106 117/117/117 118/118/118 +f 118/118/118 107/107/107 106/106/106 +f 107/107/107 118/118/118 119/119/119 +f 119/119/119 108/108/108 107/107/107 +f 108/108/108 119/119/119 120/120/120 +f 120/120/120 109/109/109 108/108/108 +f 109/109/109 120/120/120 121/121/121 +f 121/121/121 110/110/110 109/109/109 +f 111/111/111 122/122/122 123/123/123 +f 123/123/123 112/112/112 111/111/111 +f 112/112/112 123/123/123 124/124/124 +f 124/124/124 113/113/113 112/112/112 +f 113/113/113 124/124/124 125/125/125 +f 125/125/125 114/114/114 113/113/113 +f 114/114/114 125/125/125 126/126/126 +f 126/126/126 115/115/115 114/114/114 +f 115/115/115 126/126/126 127/127/127 +f 127/127/127 116/116/116 115/115/115 +f 116/116/116 127/127/127 128/128/128 +f 128/128/128 117/117/117 116/116/116 +f 117/117/117 128/128/128 129/129/129 +f 129/129/129 118/118/118 117/117/117 +f 118/118/118 129/129/129 130/130/130 +f 130/130/130 119/119/119 118/118/118 +f 119/119/119 130/130/130 131/131/131 +f 131/131/131 120/120/120 119/119/119 +f 120/120/120 131/131/131 132/132/132 +f 132/132/132 121/121/121 120/120/120 +f 122/122/122 133/133/133 134/134/134 +f 134/134/134 123/123/123 122/122/122 +f 123/123/123 134/134/134 135/135/135 +f 135/135/135 124/124/124 123/123/123 +f 124/124/124 135/135/135 136/136/136 +f 136/136/136 125/125/125 124/124/124 +f 125/125/125 136/136/136 137/137/137 +f 137/137/137 126/126/126 125/125/125 +f 126/126/126 137/137/137 138/138/138 +f 138/138/138 127/127/127 126/126/126 +f 127/127/127 138/138/138 139/139/139 +f 139/139/139 128/128/128 127/127/127 +f 128/128/128 139/139/139 140/140/140 +f 140/140/140 129/129/129 128/128/128 +f 129/129/129 140/140/140 141/141/141 +f 141/141/141 130/130/130 129/129/129 +f 130/130/130 141/141/141 142/142/142 +f 142/142/142 131/131/131 130/130/130 +f 131/131/131 142/142/142 143/143/143 +f 143/143/143 132/132/132 131/131/131 +f 133/133/133 144/144/144 145/145/145 +f 145/145/145 134/134/134 133/133/133 +f 134/134/134 145/145/145 146/146/146 +f 146/146/146 135/135/135 134/134/134 +f 135/135/135 146/146/146 147/147/147 +f 147/147/147 136/136/136 135/135/135 +f 136/136/136 147/147/147 148/148/148 +f 148/148/148 137/137/137 136/136/136 +f 137/137/137 148/148/148 149/149/149 +f 149/149/149 138/138/138 137/137/137 +f 138/138/138 149/149/149 150/150/150 +f 150/150/150 139/139/139 138/138/138 +f 139/139/139 150/150/150 151/151/151 +f 151/151/151 140/140/140 139/139/139 +f 140/140/140 151/151/151 152/152/152 +f 152/152/152 141/141/141 140/140/140 +f 141/141/141 152/152/152 153/153/153 +f 153/153/153 142/142/142 141/141/141 +f 142/142/142 153/153/153 154/154/154 +f 154/154/154 143/143/143 142/142/142 +f 144/144/144 155/155/155 156/156/156 +f 156/156/156 145/145/145 144/144/144 +f 145/145/145 156/156/156 157/157/157 +f 157/157/157 146/146/146 145/145/145 +f 146/146/146 157/157/157 158/158/158 +f 158/158/158 147/147/147 146/146/146 +f 147/147/147 158/158/158 159/159/159 +f 159/159/159 148/148/148 147/147/147 +f 148/148/148 159/159/159 160/160/160 +f 160/160/160 149/149/149 148/148/148 +f 149/149/149 160/160/160 161/161/161 +f 161/161/161 150/150/150 149/149/149 +f 150/150/150 161/161/161 162/162/162 +f 162/162/162 151/151/151 150/150/150 +f 151/151/151 162/162/162 163/163/163 +f 163/163/163 152/152/152 151/151/151 +f 152/152/152 163/163/163 164/164/164 +f 164/164/164 153/153/153 152/152/152 +f 153/153/153 164/164/164 165/165/165 +f 165/165/165 154/154/154 153/153/153 +f 155/155/155 166/166/166 167/167/167 +f 167/167/167 156/156/156 155/155/155 +f 156/156/156 167/167/167 168/168/168 +f 168/168/168 157/157/157 156/156/156 +f 157/157/157 168/168/168 169/169/169 +f 169/169/169 158/158/158 157/157/157 +f 158/158/158 169/169/169 170/170/170 +f 170/170/170 159/159/159 158/158/158 +f 159/159/159 170/170/170 171/171/171 +f 171/171/171 160/160/160 159/159/159 +f 160/160/160 171/171/171 172/172/172 +f 172/172/172 161/161/161 160/160/160 +f 161/161/161 172/172/172 173/173/173 +f 173/173/173 162/162/162 161/161/161 +f 162/162/162 173/173/173 174/174/174 +f 174/174/174 163/163/163 162/162/162 +f 163/163/163 174/174/174 175/175/175 +f 175/175/175 164/164/164 163/163/163 +f 164/164/164 175/175/175 176/176/176 +f 176/176/176 165/165/165 164/164/164 +f 166/166/166 177/177/177 178/178/178 +f 178/178/178 167/167/167 166/166/166 +f 167/167/167 178/178/178 179/179/179 +f 179/179/179 168/168/168 167/167/167 +f 168/168/168 179/179/179 180/180/180 +f 180/180/180 169/169/169 168/168/168 +f 169/169/169 180/180/180 181/181/181 +f 181/181/181 170/170/170 169/169/169 +f 170/170/170 181/181/181 182/182/182 +f 182/182/182 171/171/171 170/170/170 +f 171/171/171 182/182/182 183/183/183 +f 183/183/183 172/172/172 171/171/171 +f 172/172/172 183/183/183 184/184/184 +f 184/184/184 173/173/173 172/172/172 +f 173/173/173 184/184/184 185/185/185 +f 185/185/185 174/174/174 173/173/173 +f 174/174/174 185/185/185 186/186/186 +f 186/186/186 175/175/175 174/174/174 +f 175/175/175 186/186/186 187/187/187 +f 187/187/187 176/176/176 175/175/175 +f 177/177/177 188/188/188 189/189/189 +f 189/189/189 178/178/178 177/177/177 +f 178/178/178 189/189/189 190/190/190 +f 190/190/190 179/179/179 178/178/178 +f 179/179/179 190/190/190 191/191/191 +f 191/191/191 180/180/180 179/179/179 +f 180/180/180 191/191/191 192/192/192 +f 192/192/192 181/181/181 180/180/180 +f 181/181/181 192/192/192 193/193/193 +f 193/193/193 182/182/182 181/181/181 +f 182/182/182 193/193/193 194/194/194 +f 194/194/194 183/183/183 182/182/182 +f 183/183/183 194/194/194 195/195/195 +f 195/195/195 184/184/184 183/183/183 +f 184/184/184 195/195/195 196/196/196 +f 196/196/196 185/185/185 184/184/184 +f 185/185/185 196/196/196 197/197/197 +f 197/197/197 186/186/186 185/185/185 +f 186/186/186 197/197/197 198/198/198 +f 198/198/198 187/187/187 186/186/186 +f 188/188/188 199/199/199 200/200/200 +f 200/200/200 189/189/189 188/188/188 +f 189/189/189 200/200/200 201/201/201 +f 201/201/201 190/190/190 189/189/189 +f 190/190/190 201/201/201 202/202/202 +f 202/202/202 191/191/191 190/190/190 +f 191/191/191 202/202/202 203/203/203 +f 203/203/203 192/192/192 191/191/191 +f 192/192/192 203/203/203 204/204/204 +f 204/204/204 193/193/193 192/192/192 +f 193/193/193 204/204/204 205/205/205 +f 205/205/205 194/194/194 193/193/193 +f 194/194/194 205/205/205 206/206/206 +f 206/206/206 195/195/195 194/194/194 +f 195/195/195 206/206/206 207/207/207 +f 207/207/207 196/196/196 195/195/195 +f 196/196/196 207/207/207 208/208/208 +f 208/208/208 197/197/197 196/196/196 +f 197/197/197 208/208/208 209/209/209 +f 209/209/209 198/198/198 197/197/197 +f 199/199/199 210/210/210 211/211/211 +f 211/211/211 200/200/200 199/199/199 +f 200/200/200 211/211/211 212/212/212 +f 212/212/212 201/201/201 200/200/200 +f 201/201/201 212/212/212 213/213/213 +f 213/213/213 202/202/202 201/201/201 +f 202/202/202 213/213/213 214/214/214 +f 214/214/214 203/203/203 202/202/202 +f 203/203/203 214/214/214 215/215/215 +f 215/215/215 204/204/204 203/203/203 +f 204/204/204 215/215/215 216/216/216 +f 216/216/216 205/205/205 204/204/204 +f 205/205/205 216/216/216 217/217/217 +f 217/217/217 206/206/206 205/205/205 +f 206/206/206 217/217/217 218/218/218 +f 218/218/218 207/207/207 206/206/206 +f 207/207/207 218/218/218 219/219/219 +f 219/219/219 208/208/208 207/207/207 +f 208/208/208 219/219/219 220/220/220 +f 220/220/220 209/209/209 208/208/208 +f 210/210/210 221/221/221 222/222/222 +f 222/222/222 211/211/211 210/210/210 +f 211/211/211 222/222/222 223/223/223 +f 223/223/223 212/212/212 211/211/211 +f 212/212/212 223/223/223 224/224/224 +f 224/224/224 213/213/213 212/212/212 +f 213/213/213 224/224/224 225/225/225 +f 225/225/225 214/214/214 213/213/213 +f 214/214/214 225/225/225 226/226/226 +f 226/226/226 215/215/215 214/214/214 +f 215/215/215 226/226/226 227/227/227 +f 227/227/227 216/216/216 215/215/215 +f 216/216/216 227/227/227 228/228/228 +f 228/228/228 217/217/217 216/216/216 +f 217/217/217 228/228/228 229/229/229 +f 229/229/229 218/218/218 217/217/217 +f 218/218/218 229/229/229 230/230/230 +f 230/230/230 219/219/219 218/218/218 +f 219/219/219 230/230/230 231/231/231 +f 231/231/231 220/220/220 219/219/219 +f 221/221/221 232/232/232 233/233/233 +f 233/233/233 222/222/222 221/221/221 +f 222/222/222 233/233/233 234/234/234 +f 234/234/234 223/223/223 222/222/222 +f 223/223/223 234/234/234 235/235/235 +f 235/235/235 224/224/224 223/223/223 +f 224/224/224 235/235/235 236/236/236 +f 236/236/236 225/225/225 224/224/224 +f 225/225/225 236/236/236 237/237/237 +f 237/237/237 226/226/226 225/225/225 +f 226/226/226 237/237/237 238/238/238 +f 238/238/238 227/227/227 226/226/226 +f 227/227/227 238/238/238 239/239/239 +f 239/239/239 228/228/228 227/227/227 +f 228/228/228 239/239/239 240/240/240 +f 240/240/240 229/229/229 228/228/228 +f 229/229/229 240/240/240 241/241/241 +f 241/241/241 230/230/230 229/229/229 +f 230/230/230 241/241/241 242/242/242 +f 242/242/242 231/231/231 230/230/230 +f 232/232/232 243/243/243 244/244/244 +f 244/244/244 233/233/233 232/232/232 +f 233/233/233 244/244/244 245/245/245 +f 245/245/245 234/234/234 233/233/233 +f 234/234/234 245/245/245 246/246/246 +f 246/246/246 235/235/235 234/234/234 +f 235/235/235 246/246/246 247/247/247 +f 247/247/247 236/236/236 235/235/235 +f 236/236/236 247/247/247 248/248/248 +f 248/248/248 237/237/237 236/236/236 +f 237/237/237 248/248/248 249/249/249 +f 249/249/249 238/238/238 237/237/237 +f 238/238/238 249/249/249 250/250/250 +f 250/250/250 239/239/239 238/238/238 +f 239/239/239 250/250/250 251/251/251 +f 251/251/251 240/240/240 239/239/239 +f 240/240/240 251/251/251 252/252/252 +f 252/252/252 241/241/241 240/240/240 +f 241/241/241 252/252/252 253/253/253 +f 253/253/253 242/242/242 241/241/241 +f 243/243/243 254/254/254 255/255/255 +f 255/255/255 244/244/244 243/243/243 +f 244/244/244 255/255/255 256/256/256 +f 256/256/256 245/245/245 244/244/244 +f 245/245/245 256/256/256 257/257/257 +f 257/257/257 246/246/246 245/245/245 +f 246/246/246 257/257/257 258/258/258 +f 258/258/258 247/247/247 246/246/246 +f 247/247/247 258/258/258 259/259/259 +f 259/259/259 248/248/248 247/247/247 +f 248/248/248 259/259/259 260/260/260 +f 260/260/260 249/249/249 248/248/248 +f 249/249/249 260/260/260 261/261/261 +f 261/261/261 250/250/250 249/249/249 +f 250/250/250 261/261/261 262/262/262 +f 262/262/262 251/251/251 250/250/250 +f 251/251/251 262/262/262 263/263/263 +f 263/263/263 252/252/252 251/251/251 +f 252/252/252 263/263/263 264/264/264 +f 264/264/264 253/253/253 252/252/252 +f 254/254/254 265/265/265 266/266/266 +f 266/266/266 255/255/255 254/254/254 +f 255/255/255 266/266/266 267/267/267 +f 267/267/267 256/256/256 255/255/255 +f 256/256/256 267/267/267 268/268/268 +f 268/268/268 257/257/257 256/256/256 +f 257/257/257 268/268/268 269/269/269 +f 269/269/269 258/258/258 257/257/257 +f 258/258/258 269/269/269 270/270/270 +f 270/270/270 259/259/259 258/258/258 +f 259/259/259 270/270/270 271/271/271 +f 271/271/271 260/260/260 259/259/259 +f 260/260/260 271/271/271 272/272/272 +f 272/272/272 261/261/261 260/260/260 +f 261/261/261 272/272/272 273/273/273 +f 273/273/273 262/262/262 261/261/261 +f 262/262/262 273/273/273 274/274/274 +f 274/274/274 263/263/263 262/262/262 +f 263/263/263 274/274/274 275/275/275 +f 275/275/275 264/264/264 263/263/263 +f 265/265/265 276/276/276 277/277/277 +f 277/277/277 266/266/266 265/265/265 +f 266/266/266 277/277/277 278/278/278 +f 278/278/278 267/267/267 266/266/266 +f 267/267/267 278/278/278 279/279/279 +f 279/279/279 268/268/268 267/267/267 +f 268/268/268 279/279/279 280/280/280 +f 280/280/280 269/269/269 268/268/268 +f 269/269/269 280/280/280 281/281/281 +f 281/281/281 270/270/270 269/269/269 +f 270/270/270 281/281/281 282/282/282 +f 282/282/282 271/271/271 270/270/270 +f 271/271/271 282/282/282 283/283/283 +f 283/283/283 272/272/272 271/271/271 +f 272/272/272 283/283/283 284/284/284 +f 284/284/284 273/273/273 272/272/272 +f 273/273/273 284/284/284 285/285/285 +f 285/285/285 274/274/274 273/273/273 +f 274/274/274 285/285/285 286/286/286 +f 286/286/286 275/275/275 274/274/274 +f 276/276/276 287/287/287 288/288/288 +f 288/288/288 277/277/277 276/276/276 +f 277/277/277 288/288/288 289/289/289 +f 289/289/289 278/278/278 277/277/277 +f 278/278/278 289/289/289 290/290/290 +f 290/290/290 279/279/279 278/278/278 +f 279/279/279 290/290/290 291/291/291 +f 291/291/291 280/280/280 279/279/279 +f 280/280/280 291/291/291 292/292/292 +f 292/292/292 281/281/281 280/280/280 +f 281/281/281 292/292/292 293/293/293 +f 293/293/293 282/282/282 281/281/281 +f 282/282/282 293/293/293 294/294/294 +f 294/294/294 283/283/283 282/282/282 +f 283/283/283 294/294/294 295/295/295 +f 295/295/295 284/284/284 283/283/283 +f 284/284/284 295/295/295 296/296/296 +f 296/296/296 285/285/285 284/284/284 +f 285/285/285 296/296/296 297/297/297 +f 297/297/297 286/286/286 285/285/285 +f 287/287/287 298/298/298 299/299/299 +f 299/299/299 288/288/288 287/287/287 +f 288/288/288 299/299/299 300/300/300 +f 300/300/300 289/289/289 288/288/288 +f 289/289/289 300/300/300 301/301/301 +f 301/301/301 290/290/290 289/289/289 +f 290/290/290 301/301/301 302/302/302 +f 302/302/302 291/291/291 290/290/290 +f 291/291/291 302/302/302 303/303/303 +f 303/303/303 292/292/292 291/291/291 +f 292/292/292 303/303/303 304/304/304 +f 304/304/304 293/293/293 292/292/292 +f 293/293/293 304/304/304 305/305/305 +f 305/305/305 294/294/294 293/293/293 +f 294/294/294 305/305/305 306/306/306 +f 306/306/306 295/295/295 294/294/294 +f 295/295/295 306/306/306 307/307/307 +f 307/307/307 296/296/296 295/295/295 +f 296/296/296 307/307/307 308/308/308 +f 308/308/308 297/297/297 296/296/296 +f 298/298/298 309/309/309 310/310/310 +f 310/310/310 299/299/299 298/298/298 +f 299/299/299 310/310/310 311/311/311 +f 311/311/311 300/300/300 299/299/299 +f 300/300/300 311/311/311 312/312/312 +f 312/312/312 301/301/301 300/300/300 +f 301/301/301 312/312/312 313/313/313 +f 313/313/313 302/302/302 301/301/301 +f 302/302/302 313/313/313 314/314/314 +f 314/314/314 303/303/303 302/302/302 +f 303/303/303 314/314/314 315/315/315 +f 315/315/315 304/304/304 303/303/303 +f 304/304/304 315/315/315 316/316/316 +f 316/316/316 305/305/305 304/304/304 +f 305/305/305 316/316/316 317/317/317 +f 317/317/317 306/306/306 305/305/305 +f 306/306/306 317/317/317 318/318/318 +f 318/318/318 307/307/307 306/306/306 +f 307/307/307 318/318/318 319/319/319 +f 319/319/319 308/308/308 307/307/307 +f 309/309/309 320/320/320 321/321/321 +f 321/321/321 310/310/310 309/309/309 +f 310/310/310 321/321/321 322/322/322 +f 322/322/322 311/311/311 310/310/310 +f 311/311/311 322/322/322 323/323/323 +f 323/323/323 312/312/312 311/311/311 +f 312/312/312 323/323/323 324/324/324 +f 324/324/324 313/313/313 312/312/312 +f 313/313/313 324/324/324 325/325/325 +f 325/325/325 314/314/314 313/313/313 +f 314/314/314 325/325/325 326/326/326 +f 326/326/326 315/315/315 314/314/314 +f 315/315/315 326/326/326 327/327/327 +f 327/327/327 316/316/316 315/315/315 +f 316/316/316 327/327/327 328/328/328 +f 328/328/328 317/317/317 316/316/316 +f 317/317/317 328/328/328 329/329/329 +f 329/329/329 318/318/318 317/317/317 +f 318/318/318 329/329/329 330/330/330 +f 330/330/330 319/319/319 318/318/318 +f 320/320/320 331/331/331 332/332/332 +f 332/332/332 321/321/321 320/320/320 +f 321/321/321 332/332/332 333/333/333 +f 333/333/333 322/322/322 321/321/321 +f 322/322/322 333/333/333 334/334/334 +f 334/334/334 323/323/323 322/322/322 +f 323/323/323 334/334/334 335/335/335 +f 335/335/335 324/324/324 323/323/323 +f 324/324/324 335/335/335 336/336/336 +f 336/336/336 325/325/325 324/324/324 +f 325/325/325 336/336/336 337/337/337 +f 337/337/337 326/326/326 325/325/325 +f 326/326/326 337/337/337 338/338/338 +f 338/338/338 327/327/327 326/326/326 +f 327/327/327 338/338/338 339/339/339 +f 339/339/339 328/328/328 327/327/327 +f 328/328/328 339/339/339 340/340/340 +f 340/340/340 329/329/329 328/328/328 +f 329/329/329 340/340/340 341/341/341 +f 341/341/341 330/330/330 329/329/329 +f 331/331/331 342/342/342 343/343/343 +f 343/343/343 332/332/332 331/331/331 +f 332/332/332 343/343/343 344/344/344 +f 344/344/344 333/333/333 332/332/332 +f 333/333/333 344/344/344 345/345/345 +f 345/345/345 334/334/334 333/333/333 +f 334/334/334 345/345/345 346/346/346 +f 346/346/346 335/335/335 334/334/334 +f 335/335/335 346/346/346 347/347/347 +f 347/347/347 336/336/336 335/335/335 +f 336/336/336 347/347/347 348/348/348 +f 348/348/348 337/337/337 336/336/336 +f 337/337/337 348/348/348 349/349/349 +f 349/349/349 338/338/338 337/337/337 +f 338/338/338 349/349/349 350/350/350 +f 350/350/350 339/339/339 338/338/338 +f 339/339/339 350/350/350 351/351/351 +f 351/351/351 340/340/340 339/339/339 +f 340/340/340 351/351/351 352/352/352 +f 352/352/352 341/341/341 340/340/340 +f 342/342/342 353/353/353 354/354/354 +f 354/354/354 343/343/343 342/342/342 +f 343/343/343 354/354/354 355/355/355 +f 355/355/355 344/344/344 343/343/343 +f 344/344/344 355/355/355 356/356/356 +f 356/356/356 345/345/345 344/344/344 +f 345/345/345 356/356/356 357/357/357 +f 357/357/357 346/346/346 345/345/345 +f 346/346/346 357/357/357 358/358/358 +f 358/358/358 347/347/347 346/346/346 +f 347/347/347 358/358/358 359/359/359 +f 359/359/359 348/348/348 347/347/347 +f 348/348/348 359/359/359 360/360/360 +f 360/360/360 349/349/349 348/348/348 +f 349/349/349 360/360/360 361/361/361 +f 361/361/361 350/350/350 349/349/349 +f 350/350/350 361/361/361 362/362/362 +f 362/362/362 351/351/351 350/350/350 +f 351/351/351 362/362/362 363/363/363 +f 363/363/363 352/352/352 351/351/351 +f 353/353/353 364/364/364 365/365/365 +f 365/365/365 354/354/354 353/353/353 +f 354/354/354 365/365/365 366/366/366 +f 366/366/366 355/355/355 354/354/354 +f 355/355/355 366/366/366 367/367/367 +f 367/367/367 356/356/356 355/355/355 +f 356/356/356 367/367/367 368/368/368 +f 368/368/368 357/357/357 356/356/356 +f 357/357/357 368/368/368 369/369/369 +f 369/369/369 358/358/358 357/357/357 +f 358/358/358 369/369/369 370/370/370 +f 370/370/370 359/359/359 358/358/358 +f 359/359/359 370/370/370 371/371/371 +f 371/371/371 360/360/360 359/359/359 +f 360/360/360 371/371/371 372/372/372 +f 372/372/372 361/361/361 360/360/360 +f 361/361/361 372/372/372 373/373/373 +f 373/373/373 362/362/362 361/361/361 +f 362/362/362 373/373/373 374/374/374 +f 374/374/374 363/363/363 362/362/362 +f 364/364/364 375/375/375 376/376/376 +f 376/376/376 365/365/365 364/364/364 +f 365/365/365 376/376/376 377/377/377 +f 377/377/377 366/366/366 365/365/365 +f 366/366/366 377/377/377 378/378/378 +f 378/378/378 367/367/367 366/366/366 +f 367/367/367 378/378/378 379/379/379 +f 379/379/379 368/368/368 367/367/367 +f 368/368/368 379/379/379 380/380/380 +f 380/380/380 369/369/369 368/368/368 +f 369/369/369 380/380/380 381/381/381 +f 381/381/381 370/370/370 369/369/369 +f 370/370/370 381/381/381 382/382/382 +f 382/382/382 371/371/371 370/370/370 +f 371/371/371 382/382/382 383/383/383 +f 383/383/383 372/372/372 371/371/371 +f 372/372/372 383/383/383 384/384/384 +f 384/384/384 373/373/373 372/372/372 +f 373/373/373 384/384/384 385/385/385 +f 385/385/385 374/374/374 373/373/373 +f 375/375/375 386/386/386 387/387/387 +f 387/387/387 376/376/376 375/375/375 +f 376/376/376 387/387/387 388/388/388 +f 388/388/388 377/377/377 376/376/376 +f 377/377/377 388/388/388 389/389/389 +f 389/389/389 378/378/378 377/377/377 +f 378/378/378 389/389/389 390/390/390 +f 390/390/390 379/379/379 378/378/378 +f 379/379/379 390/390/390 391/391/391 +f 391/391/391 380/380/380 379/379/379 +f 380/380/380 391/391/391 392/392/392 +f 392/392/392 381/381/381 380/380/380 +f 381/381/381 392/392/392 393/393/393 +f 393/393/393 382/382/382 381/381/381 +f 382/382/382 393/393/393 394/394/394 +f 394/394/394 383/383/383 382/382/382 +f 383/383/383 394/394/394 395/395/395 +f 395/395/395 384/384/384 383/383/383 +f 384/384/384 395/395/395 396/396/396 +f 396/396/396 385/385/385 384/384/384 +f 386/386/386 397/397/397 398/398/398 +f 398/398/398 387/387/387 386/386/386 +f 387/387/387 398/398/398 399/399/399 +f 399/399/399 388/388/388 387/387/387 +f 388/388/388 399/399/399 400/400/400 +f 400/400/400 389/389/389 388/388/388 +f 389/389/389 400/400/400 401/401/401 +f 401/401/401 390/390/390 389/389/389 +f 390/390/390 401/401/401 402/402/402 +f 402/402/402 391/391/391 390/390/390 +f 391/391/391 402/402/402 403/403/403 +f 403/403/403 392/392/392 391/391/391 +f 392/392/392 403/403/403 404/404/404 +f 404/404/404 393/393/393 392/392/392 +f 393/393/393 404/404/404 405/405/405 +f 405/405/405 394/394/394 393/393/393 +f 394/394/394 405/405/405 406/406/406 +f 406/406/406 395/395/395 394/394/394 +f 395/395/395 406/406/406 407/407/407 +f 407/407/407 396/396/396 395/395/395 +f 397/397/397 408/408/408 409/409/409 +f 409/409/409 398/398/398 397/397/397 +f 398/398/398 409/409/409 410/410/410 +f 410/410/410 399/399/399 398/398/398 +f 399/399/399 410/410/410 411/411/411 +f 411/411/411 400/400/400 399/399/399 +f 400/400/400 411/411/411 412/412/412 +f 412/412/412 401/401/401 400/400/400 +f 401/401/401 412/412/412 413/413/413 +f 413/413/413 402/402/402 401/401/401 +f 402/402/402 413/413/413 414/414/414 +f 414/414/414 403/403/403 402/402/402 +f 403/403/403 414/414/414 415/415/415 +f 415/415/415 404/404/404 403/403/403 +f 404/404/404 415/415/415 416/416/416 +f 416/416/416 405/405/405 404/404/404 +f 405/405/405 416/416/416 417/417/417 +f 417/417/417 406/406/406 405/405/405 +f 406/406/406 417/417/417 418/418/418 +f 418/418/418 407/407/407 406/406/406 +f 408/408/408 419/419/419 420/420/420 +f 420/420/420 409/409/409 408/408/408 +f 409/409/409 420/420/420 421/421/421 +f 421/421/421 410/410/410 409/409/409 +f 410/410/410 421/421/421 422/422/422 +f 422/422/422 411/411/411 410/410/410 +f 411/411/411 422/422/422 423/423/423 +f 423/423/423 412/412/412 411/411/411 +f 412/412/412 423/423/423 424/424/424 +f 424/424/424 413/413/413 412/412/412 +f 413/413/413 424/424/424 425/425/425 +f 425/425/425 414/414/414 413/413/413 +f 414/414/414 425/425/425 426/426/426 +f 426/426/426 415/415/415 414/414/414 +f 415/415/415 426/426/426 427/427/427 +f 427/427/427 416/416/416 415/415/415 +f 416/416/416 427/427/427 428/428/428 +f 428/428/428 417/417/417 416/416/416 +f 417/417/417 428/428/428 429/429/429 +f 429/429/429 418/418/418 417/417/417 +f 419/419/419 430/430/430 431/431/431 +f 431/431/431 420/420/420 419/419/419 +f 420/420/420 431/431/431 432/432/432 +f 432/432/432 421/421/421 420/420/420 +f 421/421/421 432/432/432 433/433/433 +f 433/433/433 422/422/422 421/421/421 +f 422/422/422 433/433/433 434/434/434 +f 434/434/434 423/423/423 422/422/422 +f 423/423/423 434/434/434 435/435/435 +f 435/435/435 424/424/424 423/423/423 +f 424/424/424 435/435/435 436/436/436 +f 436/436/436 425/425/425 424/424/424 +f 425/425/425 436/436/436 437/437/437 +f 437/437/437 426/426/426 425/425/425 +f 426/426/426 437/437/437 438/438/438 +f 438/438/438 427/427/427 426/426/426 +f 427/427/427 438/438/438 439/439/439 +f 439/439/439 428/428/428 427/427/427 +f 428/428/428 439/439/439 440/440/440 +f 440/440/440 429/429/429 428/428/428 +f 430/430/430 1/1/1 4/4/4 +f 4/4/4 431/431/431 430/430/430 +f 431/431/431 4/4/4 6/6/6 +f 6/6/6 432/432/432 431/431/431 +f 432/432/432 6/6/6 8/8/8 +f 8/8/8 433/433/433 432/432/432 +f 433/433/433 8/8/8 10/10/10 +f 10/10/10 434/434/434 433/433/433 +f 434/434/434 10/10/10 12/12/12 +f 12/12/12 435/435/435 434/434/434 +f 435/435/435 12/12/12 14/14/14 +f 14/14/14 436/436/436 435/435/435 +f 436/436/436 14/14/14 16/16/16 +f 16/16/16 437/437/437 436/436/436 +f 437/437/437 16/16/16 18/18/18 +f 18/18/18 438/438/438 437/437/437 +f 438/438/438 18/18/18 20/20/20 +f 20/20/20 439/439/439 438/438/438 +f 439/439/439 20/20/20 22/22/22 +f 22/22/22 440/440/440 439/439/439 +f 22/22/22 21/21/21 441/441/441 +f 441/441/441 442/442/442 22/22/22 +f 442/442/442 441/441/441 443/443/443 +f 443/443/443 444/444/444 442/442/442 +f 444/444/444 443/443/443 445/445/445 +f 445/445/445 446/446/446 444/444/444 +f 446/446/446 445/445/445 447/447/447 +f 447/447/447 448/448/448 446/446/446 +f 448/448/448 447/447/447 449/449/449 +f 449/449/449 450/450/450 448/448/448 +f 450/450/450 449/449/449 451/451/451 +f 451/451/451 452/452/452 450/450/450 +f 452/452/452 451/451/451 453/453/453 +f 453/453/453 454/454/454 452/452/452 +f 454/454/454 453/453/453 455/455/455 +f 455/455/455 456/456/456 454/454/454 +f 456/456/456 455/455/455 457/457/457 +f 457/457/457 458/458/458 456/456/456 +f 458/458/458 457/457/457 459/459/459 +f 459/459/459 460/460/460 458/458/458 +f 21/21/21 33/33/33 461/461/461 +f 461/461/461 441/441/441 21/21/21 +f 441/441/441 461/461/461 462/462/462 +f 462/462/462 443/443/443 441/441/441 +f 443/443/443 462/462/462 463/463/463 +f 463/463/463 445/445/445 443/443/443 +f 445/445/445 463/463/463 464/464/464 +f 464/464/464 447/447/447 445/445/445 +f 447/447/447 464/464/464 465/465/465 +f 465/465/465 449/449/449 447/447/447 +f 449/449/449 465/465/465 466/466/466 +f 466/466/466 451/451/451 449/449/449 +f 451/451/451 466/466/466 467/467/467 +f 467/467/467 453/453/453 451/451/451 +f 453/453/453 467/467/467 468/468/468 +f 468/468/468 455/455/455 453/453/453 +f 455/455/455 468/468/468 469/469/469 +f 469/469/469 457/457/457 455/455/455 +f 457/457/457 469/469/469 470/470/470 +f 470/470/470 459/459/459 457/457/457 +f 33/33/33 44/44/44 471/471/471 +f 471/471/471 461/461/461 33/33/33 +f 461/461/461 471/471/471 472/472/472 +f 472/472/472 462/462/462 461/461/461 +f 462/462/462 472/472/472 473/473/473 +f 473/473/473 463/463/463 462/462/462 +f 463/463/463 473/473/473 474/474/474 +f 474/474/474 464/464/464 463/463/463 +f 464/464/464 474/474/474 475/475/475 +f 475/475/475 465/465/465 464/464/464 +f 465/465/465 475/475/475 476/476/476 +f 476/476/476 466/466/466 465/465/465 +f 466/466/466 476/476/476 477/477/477 +f 477/477/477 467/467/467 466/466/466 +f 467/467/467 477/477/477 478/478/478 +f 478/478/478 468/468/468 467/467/467 +f 468/468/468 478/478/478 479/479/479 +f 479/479/479 469/469/469 468/468/468 +f 469/469/469 479/479/479 480/480/480 +f 480/480/480 470/470/470 469/469/469 +f 44/44/44 55/55/55 481/481/481 +f 481/481/481 471/471/471 44/44/44 +f 471/471/471 481/481/481 482/482/482 +f 482/482/482 472/472/472 471/471/471 +f 472/472/472 482/482/482 483/483/483 +f 483/483/483 473/473/473 472/472/472 +f 473/473/473 483/483/483 484/484/484 +f 484/484/484 474/474/474 473/473/473 +f 474/474/474 484/484/484 485/485/485 +f 485/485/485 475/475/475 474/474/474 +f 475/475/475 485/485/485 486/486/486 +f 486/486/486 476/476/476 475/475/475 +f 476/476/476 486/486/486 487/487/487 +f 487/487/487 477/477/477 476/476/476 +f 477/477/477 487/487/487 488/488/488 +f 488/488/488 478/478/478 477/477/477 +f 478/478/478 488/488/488 489/489/489 +f 489/489/489 479/479/479 478/478/478 +f 479/479/479 489/489/489 490/490/490 +f 490/490/490 480/480/480 479/479/479 +f 55/55/55 66/66/66 491/491/491 +f 491/491/491 481/481/481 55/55/55 +f 481/481/481 491/491/491 492/492/492 +f 492/492/492 482/482/482 481/481/481 +f 482/482/482 492/492/492 493/493/493 +f 493/493/493 483/483/483 482/482/482 +f 483/483/483 493/493/493 494/494/494 +f 494/494/494 484/484/484 483/483/483 +f 484/484/484 494/494/494 495/495/495 +f 495/495/495 485/485/485 484/484/484 +f 485/485/485 495/495/495 496/496/496 +f 496/496/496 486/486/486 485/485/485 +f 486/486/486 496/496/496 497/497/497 +f 497/497/497 487/487/487 486/486/486 +f 487/487/487 497/497/497 498/498/498 +f 498/498/498 488/488/488 487/487/487 +f 488/488/488 498/498/498 499/499/499 +f 499/499/499 489/489/489 488/488/488 +f 489/489/489 499/499/499 500/500/500 +f 500/500/500 490/490/490 489/489/489 +f 66/66/66 77/77/77 501/501/501 +f 501/501/501 491/491/491 66/66/66 +f 491/491/491 501/501/501 502/502/502 +f 502/502/502 492/492/492 491/491/491 +f 492/492/492 502/502/502 503/503/503 +f 503/503/503 493/493/493 492/492/492 +f 493/493/493 503/503/503 504/504/504 +f 504/504/504 494/494/494 493/493/493 +f 494/494/494 504/504/504 505/505/505 +f 505/505/505 495/495/495 494/494/494 +f 495/495/495 505/505/505 506/506/506 +f 506/506/506 496/496/496 495/495/495 +f 496/496/496 506/506/506 507/507/507 +f 507/507/507 497/497/497 496/496/496 +f 497/497/497 507/507/507 508/508/508 +f 508/508/508 498/498/498 497/497/497 +f 498/498/498 508/508/508 509/509/509 +f 509/509/509 499/499/499 498/498/498 +f 499/499/499 509/509/509 510/510/510 +f 510/510/510 500/500/500 499/499/499 +f 77/77/77 88/88/88 511/511/511 +f 511/511/511 501/501/501 77/77/77 +f 501/501/501 511/511/511 512/512/512 +f 512/512/512 502/502/502 501/501/501 +f 502/502/502 512/512/512 513/513/513 +f 513/513/513 503/503/503 502/502/502 +f 503/503/503 513/513/513 514/514/514 +f 514/514/514 504/504/504 503/503/503 +f 504/504/504 514/514/514 515/515/515 +f 515/515/515 505/505/505 504/504/504 +f 505/505/505 515/515/515 516/516/516 +f 516/516/516 506/506/506 505/505/505 +f 506/506/506 516/516/516 517/517/517 +f 517/517/517 507/507/507 506/506/506 +f 507/507/507 517/517/517 518/518/518 +f 518/518/518 508/508/508 507/507/507 +f 508/508/508 518/518/518 519/519/519 +f 519/519/519 509/509/509 508/508/508 +f 509/509/509 519/519/519 520/520/520 +f 520/520/520 510/510/510 509/509/509 +f 88/88/88 99/99/99 521/521/521 +f 521/521/521 511/511/511 88/88/88 +f 511/511/511 521/521/521 522/522/522 +f 522/522/522 512/512/512 511/511/511 +f 512/512/512 522/522/522 523/523/523 +f 523/523/523 513/513/513 512/512/512 +f 513/513/513 523/523/523 524/524/524 +f 524/524/524 514/514/514 513/513/513 +f 514/514/514 524/524/524 525/525/525 +f 525/525/525 515/515/515 514/514/514 +f 515/515/515 525/525/525 526/526/526 +f 526/526/526 516/516/516 515/515/515 +f 516/516/516 526/526/526 527/527/527 +f 527/527/527 517/517/517 516/516/516 +f 517/517/517 527/527/527 528/528/528 +f 528/528/528 518/518/518 517/517/517 +f 518/518/518 528/528/528 529/529/529 +f 529/529/529 519/519/519 518/518/518 +f 519/519/519 529/529/529 530/530/530 +f 530/530/530 520/520/520 519/519/519 +f 99/99/99 110/110/110 531/531/531 +f 531/531/531 521/521/521 99/99/99 +f 521/521/521 531/531/531 532/532/532 +f 532/532/532 522/522/522 521/521/521 +f 522/522/522 532/532/532 533/533/533 +f 533/533/533 523/523/523 522/522/522 +f 523/523/523 533/533/533 534/534/534 +f 534/534/534 524/524/524 523/523/523 +f 524/524/524 534/534/534 535/535/535 +f 535/535/535 525/525/525 524/524/524 +f 525/525/525 535/535/535 536/536/536 +f 536/536/536 526/526/526 525/525/525 +f 526/526/526 536/536/536 537/537/537 +f 537/537/537 527/527/527 526/526/526 +f 527/527/527 537/537/537 538/538/538 +f 538/538/538 528/528/528 527/527/527 +f 528/528/528 538/538/538 539/539/539 +f 539/539/539 529/529/529 528/528/528 +f 529/529/529 539/539/539 540/540/540 +f 540/540/540 530/530/530 529/529/529 +f 110/110/110 121/121/121 541/541/541 +f 541/541/541 531/531/531 110/110/110 +f 531/531/531 541/541/541 542/542/542 +f 542/542/542 532/532/532 531/531/531 +f 532/532/532 542/542/542 543/543/543 +f 543/543/543 533/533/533 532/532/532 +f 533/533/533 543/543/543 544/544/544 +f 544/544/544 534/534/534 533/533/533 +f 534/534/534 544/544/544 545/545/545 +f 545/545/545 535/535/535 534/534/534 +f 535/535/535 545/545/545 546/546/546 +f 546/546/546 536/536/536 535/535/535 +f 536/536/536 546/546/546 547/547/547 +f 547/547/547 537/537/537 536/536/536 +f 537/537/537 547/547/547 548/548/548 +f 548/548/548 538/538/538 537/537/537 +f 538/538/538 548/548/548 549/549/549 +f 549/549/549 539/539/539 538/538/538 +f 539/539/539 549/549/549 550/550/550 +f 550/550/550 540/540/540 539/539/539 +f 121/121/121 132/132/132 551/551/551 +f 551/551/551 541/541/541 121/121/121 +f 541/541/541 551/551/551 552/552/552 +f 552/552/552 542/542/542 541/541/541 +f 542/542/542 552/552/552 553/553/553 +f 553/553/553 543/543/543 542/542/542 +f 543/543/543 553/553/553 554/554/554 +f 554/554/554 544/544/544 543/543/543 +f 544/544/544 554/554/554 555/555/555 +f 555/555/555 545/545/545 544/544/544 +f 545/545/545 555/555/555 556/556/556 +f 556/556/556 546/546/546 545/545/545 +f 546/546/546 556/556/556 557/557/557 +f 557/557/557 547/547/547 546/546/546 +f 547/547/547 557/557/557 558/558/558 +f 558/558/558 548/548/548 547/547/547 +f 548/548/548 558/558/558 559/559/559 +f 559/559/559 549/549/549 548/548/548 +f 549/549/549 559/559/559 560/560/560 +f 560/560/560 550/550/550 549/549/549 +f 132/132/132 143/143/143 561/561/561 +f 561/561/561 551/551/551 132/132/132 +f 551/551/551 561/561/561 562/562/562 +f 562/562/562 552/552/552 551/551/551 +f 552/552/552 562/562/562 563/563/563 +f 563/563/563 553/553/553 552/552/552 +f 553/553/553 563/563/563 564/564/564 +f 564/564/564 554/554/554 553/553/553 +f 554/554/554 564/564/564 565/565/565 +f 565/565/565 555/555/555 554/554/554 +f 555/555/555 565/565/565 566/566/566 +f 566/566/566 556/556/556 555/555/555 +f 556/556/556 566/566/566 567/567/567 +f 567/567/567 557/557/557 556/556/556 +f 557/557/557 567/567/567 568/568/568 +f 568/568/568 558/558/558 557/557/557 +f 558/558/558 568/568/568 569/569/569 +f 569/569/569 559/559/559 558/558/558 +f 559/559/559 569/569/569 570/570/570 +f 570/570/570 560/560/560 559/559/559 +f 143/143/143 154/154/154 571/571/571 +f 571/571/571 561/561/561 143/143/143 +f 561/561/561 571/571/571 572/572/572 +f 572/572/572 562/562/562 561/561/561 +f 562/562/562 572/572/572 573/573/573 +f 573/573/573 563/563/563 562/562/562 +f 563/563/563 573/573/573 574/574/574 +f 574/574/574 564/564/564 563/563/563 +f 564/564/564 574/574/574 575/575/575 +f 575/575/575 565/565/565 564/564/564 +f 565/565/565 575/575/575 576/576/576 +f 576/576/576 566/566/566 565/565/565 +f 566/566/566 576/576/576 577/577/577 +f 577/577/577 567/567/567 566/566/566 +f 567/567/567 577/577/577 578/578/578 +f 578/578/578 568/568/568 567/567/567 +f 568/568/568 578/578/578 579/579/579 +f 579/579/579 569/569/569 568/568/568 +f 569/569/569 579/579/579 580/580/580 +f 580/580/580 570/570/570 569/569/569 +f 154/154/154 165/165/165 581/581/581 +f 581/581/581 571/571/571 154/154/154 +f 571/571/571 581/581/581 582/582/582 +f 582/582/582 572/572/572 571/571/571 +f 572/572/572 582/582/582 583/583/583 +f 583/583/583 573/573/573 572/572/572 +f 573/573/573 583/583/583 584/584/584 +f 584/584/584 574/574/574 573/573/573 +f 574/574/574 584/584/584 585/585/585 +f 585/585/585 575/575/575 574/574/574 +f 575/575/575 585/585/585 586/586/586 +f 586/586/586 576/576/576 575/575/575 +f 576/576/576 586/586/586 587/587/587 +f 587/587/587 577/577/577 576/576/576 +f 577/577/577 587/587/587 588/588/588 +f 588/588/588 578/578/578 577/577/577 +f 578/578/578 588/588/588 589/589/589 +f 589/589/589 579/579/579 578/578/578 +f 579/579/579 589/589/589 590/590/590 +f 590/590/590 580/580/580 579/579/579 +f 165/165/165 176/176/176 591/591/591 +f 591/591/591 581/581/581 165/165/165 +f 581/581/581 591/591/591 592/592/592 +f 592/592/592 582/582/582 581/581/581 +f 582/582/582 592/592/592 593/593/593 +f 593/593/593 583/583/583 582/582/582 +f 583/583/583 593/593/593 594/594/594 +f 594/594/594 584/584/584 583/583/583 +f 584/584/584 594/594/594 595/595/595 +f 595/595/595 585/585/585 584/584/584 +f 585/585/585 595/595/595 596/596/596 +f 596/596/596 586/586/586 585/585/585 +f 586/586/586 596/596/596 597/597/597 +f 597/597/597 587/587/587 586/586/586 +f 587/587/587 597/597/597 598/598/598 +f 598/598/598 588/588/588 587/587/587 +f 588/588/588 598/598/598 599/599/599 +f 599/599/599 589/589/589 588/588/588 +f 589/589/589 599/599/599 600/600/600 +f 600/600/600 590/590/590 589/589/589 +f 176/176/176 187/187/187 601/601/601 +f 601/601/601 591/591/591 176/176/176 +f 591/591/591 601/601/601 602/602/602 +f 602/602/602 592/592/592 591/591/591 +f 592/592/592 602/602/602 603/603/603 +f 603/603/603 593/593/593 592/592/592 +f 593/593/593 603/603/603 604/604/604 +f 604/604/604 594/594/594 593/593/593 +f 594/594/594 604/604/604 605/605/605 +f 605/605/605 595/595/595 594/594/594 +f 595/595/595 605/605/605 606/606/606 +f 606/606/606 596/596/596 595/595/595 +f 596/596/596 606/606/606 607/607/607 +f 607/607/607 597/597/597 596/596/596 +f 597/597/597 607/607/607 608/608/608 +f 608/608/608 598/598/598 597/597/597 +f 598/598/598 608/608/608 609/609/609 +f 609/609/609 599/599/599 598/598/598 +f 599/599/599 609/609/609 610/610/610 +f 610/610/610 600/600/600 599/599/599 +f 187/187/187 198/198/198 611/611/611 +f 611/611/611 601/601/601 187/187/187 +f 601/601/601 611/611/611 612/612/612 +f 612/612/612 602/602/602 601/601/601 +f 602/602/602 612/612/612 613/613/613 +f 613/613/613 603/603/603 602/602/602 +f 603/603/603 613/613/613 614/614/614 +f 614/614/614 604/604/604 603/603/603 +f 604/604/604 614/614/614 615/615/615 +f 615/615/615 605/605/605 604/604/604 +f 605/605/605 615/615/615 616/616/616 +f 616/616/616 606/606/606 605/605/605 +f 606/606/606 616/616/616 617/617/617 +f 617/617/617 607/607/607 606/606/606 +f 607/607/607 617/617/617 618/618/618 +f 618/618/618 608/608/608 607/607/607 +f 608/608/608 618/618/618 619/619/619 +f 619/619/619 609/609/609 608/608/608 +f 609/609/609 619/619/619 620/620/620 +f 620/620/620 610/610/610 609/609/609 +f 198/198/198 209/209/209 621/621/621 +f 621/621/621 611/611/611 198/198/198 +f 611/611/611 621/621/621 622/622/622 +f 622/622/622 612/612/612 611/611/611 +f 612/612/612 622/622/622 623/623/623 +f 623/623/623 613/613/613 612/612/612 +f 613/613/613 623/623/623 624/624/624 +f 624/624/624 614/614/614 613/613/613 +f 614/614/614 624/624/624 625/625/625 +f 625/625/625 615/615/615 614/614/614 +f 615/615/615 625/625/625 626/626/626 +f 626/626/626 616/616/616 615/615/615 +f 616/616/616 626/626/626 627/627/627 +f 627/627/627 617/617/617 616/616/616 +f 617/617/617 627/627/627 628/628/628 +f 628/628/628 618/618/618 617/617/617 +f 618/618/618 628/628/628 629/629/629 +f 629/629/629 619/619/619 618/618/618 +f 619/619/619 629/629/629 630/630/630 +f 630/630/630 620/620/620 619/619/619 +f 209/209/209 220/220/220 631/631/631 +f 631/631/631 621/621/621 209/209/209 +f 621/621/621 631/631/631 632/632/632 +f 632/632/632 622/622/622 621/621/621 +f 622/622/622 632/632/632 633/633/633 +f 633/633/633 623/623/623 622/622/622 +f 623/623/623 633/633/633 634/634/634 +f 634/634/634 624/624/624 623/623/623 +f 624/624/624 634/634/634 635/635/635 +f 635/635/635 625/625/625 624/624/624 +f 625/625/625 635/635/635 636/636/636 +f 636/636/636 626/626/626 625/625/625 +f 626/626/626 636/636/636 637/637/637 +f 637/637/637 627/627/627 626/626/626 +f 627/627/627 637/637/637 638/638/638 +f 638/638/638 628/628/628 627/627/627 +f 628/628/628 638/638/638 639/639/639 +f 639/639/639 629/629/629 628/628/628 +f 629/629/629 639/639/639 640/640/640 +f 640/640/640 630/630/630 629/629/629 +f 220/220/220 231/231/231 641/641/641 +f 641/641/641 631/631/631 220/220/220 +f 631/631/631 641/641/641 642/642/642 +f 642/642/642 632/632/632 631/631/631 +f 632/632/632 642/642/642 643/643/643 +f 643/643/643 633/633/633 632/632/632 +f 633/633/633 643/643/643 644/644/644 +f 644/644/644 634/634/634 633/633/633 +f 634/634/634 644/644/644 645/645/645 +f 645/645/645 635/635/635 634/634/634 +f 635/635/635 645/645/645 646/646/646 +f 646/646/646 636/636/636 635/635/635 +f 636/636/636 646/646/646 647/647/647 +f 647/647/647 637/637/637 636/636/636 +f 637/637/637 647/647/647 648/648/648 +f 648/648/648 638/638/638 637/637/637 +f 638/638/638 648/648/648 649/649/649 +f 649/649/649 639/639/639 638/638/638 +f 639/639/639 649/649/649 1840/650/650 +f 1840/650/650 640/640/640 639/639/639 +f 231/231/231 242/242/242 650/651/651 +f 650/651/651 641/641/641 231/231/231 +f 641/641/641 650/651/651 651/652/652 +f 651/652/652 642/642/642 641/641/641 +f 642/642/642 651/652/652 652/653/653 +f 652/653/653 643/643/643 642/642/642 +f 643/643/643 652/653/653 653/654/654 +f 653/654/654 644/644/644 643/643/643 +f 644/644/644 653/654/654 654/655/655 +f 654/655/655 645/645/645 644/644/644 +f 645/645/645 654/655/655 655/656/656 +f 655/656/656 646/646/646 645/645/645 +f 646/646/646 655/656/656 656/657/657 +f 656/657/657 647/647/647 646/646/646 +f 647/647/647 656/657/657 657/658/658 +f 657/658/658 648/648/648 647/647/647 +f 648/648/648 657/658/658 658/659/659 +f 658/659/659 649/649/649 648/648/648 +f 649/649/649 658/659/659 659/660/660 +f 659/660/660 1840/650/650 649/649/649 +f 242/242/242 253/253/253 660/661/661 +f 660/661/661 650/651/651 242/242/242 +f 650/651/651 660/661/661 661/662/662 +f 661/662/662 651/652/652 650/651/651 +f 651/652/652 661/662/662 662/663/663 +f 662/663/663 652/653/653 651/652/652 +f 652/653/653 662/663/663 663/664/664 +f 663/664/664 653/654/654 652/653/653 +f 653/654/654 663/664/664 664/665/665 +f 664/665/665 654/655/655 653/654/654 +f 654/655/655 664/665/665 665/666/666 +f 665/666/666 655/656/656 654/655/655 +f 655/656/656 665/666/666 666/667/667 +f 666/667/667 656/657/657 655/656/656 +f 656/657/657 666/667/667 667/668/668 +f 667/668/668 657/658/658 656/657/657 +f 657/658/658 667/668/668 668/669/669 +f 668/669/669 658/659/659 657/658/658 +f 658/659/659 668/669/669 669/670/670 +f 669/670/670 659/660/660 658/659/659 +f 253/253/253 264/264/264 670/671/671 +f 670/671/671 660/661/661 253/253/253 +f 660/661/661 670/671/671 671/672/672 +f 671/672/672 661/662/662 660/661/661 +f 661/662/662 671/672/672 672/673/673 +f 672/673/673 662/663/663 661/662/662 +f 662/663/663 672/673/673 673/674/674 +f 673/674/674 663/664/664 662/663/663 +f 663/664/664 673/674/674 674/675/675 +f 674/675/675 664/665/665 663/664/664 +f 664/665/665 674/675/675 675/676/676 +f 675/676/676 665/666/666 664/665/665 +f 665/666/666 675/676/676 676/677/677 +f 676/677/677 666/667/667 665/666/666 +f 666/667/667 676/677/677 677/678/678 +f 677/678/678 667/668/668 666/667/667 +f 667/668/668 677/678/678 678/679/679 +f 678/679/679 668/669/669 667/668/668 +f 668/669/669 678/679/679 679/680/680 +f 679/680/680 669/670/670 668/669/669 +f 264/264/264 275/275/275 680/681/681 +f 680/681/681 670/671/671 264/264/264 +f 670/671/671 680/681/681 681/682/682 +f 681/682/682 671/672/672 670/671/671 +f 671/672/672 681/682/682 682/683/683 +f 682/683/683 672/673/673 671/672/672 +f 672/673/673 682/683/683 683/684/684 +f 683/684/684 673/674/674 672/673/673 +f 673/674/674 683/684/684 684/685/685 +f 684/685/685 674/675/675 673/674/674 +f 674/675/675 684/685/685 685/686/686 +f 685/686/686 675/676/676 674/675/675 +f 675/676/676 685/686/686 686/687/687 +f 686/687/687 676/677/677 675/676/676 +f 676/677/677 686/687/687 687/688/688 +f 687/688/688 677/678/678 676/677/677 +f 677/678/678 687/688/688 688/689/689 +f 688/689/689 678/679/679 677/678/678 +f 678/679/679 688/689/689 689/690/690 +f 689/690/690 679/680/680 678/679/679 +f 275/275/275 286/286/286 690/691/691 +f 690/691/691 680/681/681 275/275/275 +f 680/681/681 690/691/691 691/692/692 +f 691/692/692 681/682/682 680/681/681 +f 681/682/682 691/692/692 692/693/693 +f 692/693/693 682/683/683 681/682/682 +f 682/683/683 692/693/693 693/694/694 +f 693/694/694 683/684/684 682/683/683 +f 683/684/684 693/694/694 694/695/695 +f 694/695/695 684/685/685 683/684/684 +f 684/685/685 694/695/695 695/696/696 +f 695/696/696 685/686/686 684/685/685 +f 685/686/686 695/696/696 696/697/697 +f 696/697/697 686/687/687 685/686/686 +f 686/687/687 696/697/697 697/698/698 +f 697/698/698 687/688/688 686/687/687 +f 687/688/688 697/698/698 698/699/699 +f 698/699/699 688/689/689 687/688/688 +f 688/689/689 698/699/699 699/700/700 +f 699/700/700 689/690/690 688/689/689 +f 286/286/286 297/297/297 700/701/701 +f 700/701/701 690/691/691 286/286/286 +f 690/691/691 700/701/701 701/702/702 +f 701/702/702 691/692/692 690/691/691 +f 691/692/692 701/702/702 702/703/703 +f 702/703/703 692/693/693 691/692/692 +f 692/693/693 702/703/703 703/704/704 +f 703/704/704 693/694/694 692/693/693 +f 693/694/694 703/704/704 704/705/705 +f 704/705/705 694/695/695 693/694/694 +f 694/695/695 704/705/705 705/706/706 +f 705/706/706 695/696/696 694/695/695 +f 695/696/696 705/706/706 706/707/707 +f 706/707/707 696/697/697 695/696/696 +f 696/697/697 706/707/707 707/708/708 +f 707/708/708 697/698/698 696/697/697 +f 697/698/698 707/708/708 708/709/709 +f 708/709/709 698/699/699 697/698/698 +f 698/699/699 708/709/709 709/710/710 +f 709/710/710 699/700/700 698/699/699 +f 297/297/297 308/308/308 710/711/711 +f 710/711/711 700/701/701 297/297/297 +f 700/701/701 710/711/711 711/712/712 +f 711/712/712 701/702/702 700/701/701 +f 701/702/702 711/712/712 712/713/713 +f 712/713/713 702/703/703 701/702/702 +f 702/703/703 712/713/713 713/714/714 +f 713/714/714 703/704/704 702/703/703 +f 703/704/704 713/714/714 714/715/715 +f 714/715/715 704/705/705 703/704/704 +f 704/705/705 714/715/715 715/716/716 +f 715/716/716 705/706/706 704/705/705 +f 705/706/706 715/716/716 716/717/717 +f 716/717/717 706/707/707 705/706/706 +f 706/707/707 716/717/717 717/718/718 +f 717/718/718 707/708/708 706/707/707 +f 707/708/708 717/718/718 718/719/719 +f 718/719/719 708/709/709 707/708/708 +f 708/709/709 718/719/719 719/720/720 +f 719/720/720 709/710/710 708/709/709 +f 308/308/308 319/319/319 720/721/721 +f 720/721/721 710/711/711 308/308/308 +f 710/711/711 720/721/721 721/722/722 +f 721/722/722 711/712/712 710/711/711 +f 711/712/712 721/722/722 722/723/723 +f 722/723/723 712/713/713 711/712/712 +f 712/713/713 722/723/723 723/724/724 +f 723/724/724 713/714/714 712/713/713 +f 713/714/714 723/724/724 724/725/725 +f 724/725/725 714/715/715 713/714/714 +f 714/715/715 724/725/725 725/726/726 +f 725/726/726 715/716/716 714/715/715 +f 715/716/716 725/726/726 726/727/727 +f 726/727/727 716/717/717 715/716/716 +f 716/717/717 726/727/727 727/728/728 +f 727/728/728 717/718/718 716/717/717 +f 717/718/718 727/728/728 728/729/729 +f 728/729/729 718/719/719 717/718/718 +f 718/719/719 728/729/729 729/730/730 +f 729/730/730 719/720/720 718/719/719 +f 319/319/319 330/330/330 730/731/731 +f 730/731/731 720/721/721 319/319/319 +f 720/721/721 730/731/731 731/732/732 +f 731/732/732 721/722/722 720/721/721 +f 721/722/722 731/732/732 732/733/733 +f 732/733/733 722/723/723 721/722/722 +f 722/723/723 732/733/733 733/734/734 +f 733/734/734 723/724/724 722/723/723 +f 723/724/724 733/734/734 734/735/735 +f 734/735/735 724/725/725 723/724/724 +f 724/725/725 734/735/735 735/736/736 +f 735/736/736 725/726/726 724/725/725 +f 725/726/726 735/736/736 736/737/737 +f 736/737/737 726/727/727 725/726/726 +f 726/727/727 736/737/737 737/738/738 +f 737/738/738 727/728/728 726/727/727 +f 727/728/728 737/738/738 738/739/739 +f 738/739/739 728/729/729 727/728/728 +f 728/729/729 738/739/739 739/740/740 +f 739/740/740 729/730/730 728/729/729 +f 330/330/330 341/341/341 740/741/741 +f 740/741/741 730/731/731 330/330/330 +f 730/731/731 740/741/741 741/742/742 +f 741/742/742 731/732/732 730/731/731 +f 731/732/732 741/742/742 742/743/743 +f 742/743/743 732/733/733 731/732/732 +f 732/733/733 742/743/743 743/744/744 +f 743/744/744 733/734/734 732/733/733 +f 733/734/734 743/744/744 744/745/745 +f 744/745/745 734/735/735 733/734/734 +f 734/735/735 744/745/745 745/746/746 +f 745/746/746 735/736/736 734/735/735 +f 735/736/736 745/746/746 746/747/747 +f 746/747/747 736/737/737 735/736/736 +f 736/737/737 746/747/747 747/748/748 +f 747/748/748 737/738/738 736/737/737 +f 737/738/738 747/748/748 748/749/749 +f 748/749/749 738/739/739 737/738/738 +f 738/739/739 748/749/749 749/750/750 +f 749/750/750 739/740/740 738/739/739 +f 341/341/341 352/352/352 750/751/751 +f 750/751/751 740/741/741 341/341/341 +f 740/741/741 750/751/751 751/752/752 +f 751/752/752 741/742/742 740/741/741 +f 741/742/742 751/752/752 752/753/753 +f 752/753/753 742/743/743 741/742/742 +f 742/743/743 752/753/753 753/754/754 +f 753/754/754 743/744/744 742/743/743 +f 743/744/744 753/754/754 754/755/755 +f 754/755/755 744/745/745 743/744/744 +f 744/745/745 754/755/755 755/756/756 +f 755/756/756 745/746/746 744/745/745 +f 745/746/746 755/756/756 756/757/757 +f 756/757/757 746/747/747 745/746/746 +f 746/747/747 756/757/757 757/758/758 +f 757/758/758 747/748/748 746/747/747 +f 747/748/748 757/758/758 758/759/759 +f 758/759/759 748/749/749 747/748/748 +f 748/749/749 758/759/759 759/760/760 +f 759/760/760 749/750/750 748/749/749 +f 352/352/352 363/363/363 760/761/761 +f 760/761/761 750/751/751 352/352/352 +f 750/751/751 760/761/761 761/762/762 +f 761/762/762 751/752/752 750/751/751 +f 751/752/752 761/762/762 762/763/763 +f 762/763/763 752/753/753 751/752/752 +f 752/753/753 762/763/763 763/764/764 +f 763/764/764 753/754/754 752/753/753 +f 753/754/754 763/764/764 764/765/765 +f 764/765/765 754/755/755 753/754/754 +f 754/755/755 764/765/765 765/766/766 +f 765/766/766 755/756/756 754/755/755 +f 755/756/756 765/766/766 766/767/767 +f 766/767/767 756/757/757 755/756/756 +f 756/757/757 766/767/767 767/768/768 +f 767/768/768 757/758/758 756/757/757 +f 757/758/758 767/768/768 768/769/769 +f 768/769/769 758/759/759 757/758/758 +f 758/759/759 768/769/769 769/770/770 +f 769/770/770 759/760/760 758/759/759 +f 363/363/363 374/374/374 770/771/771 +f 770/771/771 760/761/761 363/363/363 +f 760/761/761 770/771/771 771/772/772 +f 771/772/772 761/762/762 760/761/761 +f 761/762/762 771/772/772 772/773/773 +f 772/773/773 762/763/763 761/762/762 +f 762/763/763 772/773/773 773/774/774 +f 773/774/774 763/764/764 762/763/763 +f 763/764/764 773/774/774 774/775/775 +f 774/775/775 764/765/765 763/764/764 +f 764/765/765 774/775/775 775/776/776 +f 775/776/776 765/766/766 764/765/765 +f 765/766/766 775/776/776 776/777/777 +f 776/777/777 766/767/767 765/766/766 +f 766/767/767 776/777/777 777/778/778 +f 777/778/778 767/768/768 766/767/767 +f 767/768/768 777/778/778 778/779/779 +f 778/779/779 768/769/769 767/768/768 +f 768/769/769 778/779/779 779/780/780 +f 779/780/780 769/770/770 768/769/769 +f 374/374/374 385/385/385 780/781/781 +f 780/781/781 770/771/771 374/374/374 +f 770/771/771 780/781/781 781/782/782 +f 781/782/782 771/772/772 770/771/771 +f 771/772/772 781/782/782 782/783/783 +f 782/783/783 772/773/773 771/772/772 +f 772/773/773 782/783/783 783/784/784 +f 783/784/784 773/774/774 772/773/773 +f 773/774/774 783/784/784 784/785/785 +f 784/785/785 774/775/775 773/774/774 +f 774/775/775 784/785/785 785/786/786 +f 785/786/786 775/776/776 774/775/775 +f 775/776/776 785/786/786 786/787/787 +f 786/787/787 776/777/777 775/776/776 +f 776/777/777 786/787/787 787/788/788 +f 787/788/788 777/778/778 776/777/777 +f 777/778/778 787/788/788 788/789/789 +f 788/789/789 778/779/779 777/778/778 +f 778/779/779 788/789/789 789/790/790 +f 789/790/790 779/780/780 778/779/779 +f 385/385/385 396/396/396 790/791/791 +f 790/791/791 780/781/781 385/385/385 +f 780/781/781 790/791/791 791/792/792 +f 791/792/792 781/782/782 780/781/781 +f 781/782/782 791/792/792 792/793/793 +f 792/793/793 782/783/783 781/782/782 +f 782/783/783 792/793/793 793/794/794 +f 793/794/794 783/784/784 782/783/783 +f 783/784/784 793/794/794 794/795/795 +f 794/795/795 784/785/785 783/784/784 +f 784/785/785 794/795/795 795/796/796 +f 795/796/796 785/786/786 784/785/785 +f 785/786/786 795/796/796 796/797/797 +f 796/797/797 786/787/787 785/786/786 +f 786/787/787 796/797/797 797/798/798 +f 797/798/798 787/788/788 786/787/787 +f 787/788/788 797/798/798 798/799/799 +f 798/799/799 788/789/789 787/788/788 +f 788/789/789 798/799/799 799/800/800 +f 799/800/800 789/790/790 788/789/789 +f 396/396/396 407/407/407 800/801/801 +f 800/801/801 790/791/791 396/396/396 +f 790/791/791 800/801/801 801/802/802 +f 801/802/802 791/792/792 790/791/791 +f 791/792/792 801/802/802 802/803/803 +f 802/803/803 792/793/793 791/792/792 +f 792/793/793 802/803/803 803/804/804 +f 803/804/804 793/794/794 792/793/793 +f 793/794/794 803/804/804 804/805/805 +f 804/805/805 794/795/795 793/794/794 +f 794/795/795 804/805/805 805/806/806 +f 805/806/806 795/796/796 794/795/795 +f 795/796/796 805/806/806 806/807/807 +f 806/807/807 796/797/797 795/796/796 +f 796/797/797 806/807/807 807/808/808 +f 807/808/808 797/798/798 796/797/797 +f 797/798/798 807/808/808 808/809/809 +f 808/809/809 798/799/799 797/798/798 +f 798/799/799 808/809/809 809/810/810 +f 809/810/810 799/800/800 798/799/799 +f 407/407/407 418/418/418 810/811/811 +f 810/811/811 800/801/801 407/407/407 +f 800/801/801 810/811/811 811/812/812 +f 811/812/812 801/802/802 800/801/801 +f 801/802/802 811/812/812 812/813/813 +f 812/813/813 802/803/803 801/802/802 +f 802/803/803 812/813/813 813/814/814 +f 813/814/814 803/804/804 802/803/803 +f 803/804/804 813/814/814 814/815/815 +f 814/815/815 804/805/805 803/804/804 +f 804/805/805 814/815/815 815/816/816 +f 815/816/816 805/806/806 804/805/805 +f 805/806/806 815/816/816 816/817/817 +f 816/817/817 806/807/807 805/806/806 +f 806/807/807 816/817/817 817/818/818 +f 817/818/818 807/808/808 806/807/807 +f 807/808/808 817/818/818 818/819/819 +f 818/819/819 808/809/809 807/808/808 +f 808/809/809 818/819/819 819/820/820 +f 819/820/820 809/810/810 808/809/809 +f 418/418/418 429/429/429 820/821/821 +f 820/821/821 810/811/811 418/418/418 +f 810/811/811 820/821/821 821/822/822 +f 821/822/822 811/812/812 810/811/811 +f 811/812/812 821/822/822 822/823/823 +f 822/823/823 812/813/813 811/812/812 +f 812/813/813 822/823/823 823/824/824 +f 823/824/824 813/814/814 812/813/813 +f 813/814/814 823/824/824 824/825/825 +f 824/825/825 814/815/815 813/814/814 +f 814/815/815 824/825/825 825/826/826 +f 825/826/826 815/816/816 814/815/815 +f 815/816/816 825/826/826 826/827/827 +f 826/827/827 816/817/817 815/816/816 +f 816/817/817 826/827/827 827/828/828 +f 827/828/828 817/818/818 816/817/817 +f 817/818/818 827/828/828 828/829/829 +f 828/829/829 818/819/819 817/818/818 +f 818/819/819 828/829/829 829/830/830 +f 829/830/830 819/820/820 818/819/819 +f 429/429/429 440/440/440 830/831/831 +f 830/831/831 820/821/821 429/429/429 +f 820/821/821 830/831/831 831/832/832 +f 831/832/832 821/822/822 820/821/821 +f 821/822/822 831/832/832 832/833/833 +f 832/833/833 822/823/823 821/822/822 +f 822/823/823 832/833/833 833/834/834 +f 833/834/834 823/824/824 822/823/823 +f 823/824/824 833/834/834 834/835/835 +f 834/835/835 824/825/825 823/824/824 +f 824/825/825 834/835/835 835/836/836 +f 835/836/836 825/826/826 824/825/825 +f 825/826/826 835/836/836 836/837/837 +f 836/837/837 826/827/827 825/826/826 +f 826/827/827 836/837/837 837/838/838 +f 837/838/838 827/828/828 826/827/827 +f 827/828/828 837/838/838 838/839/839 +f 838/839/839 828/829/829 827/828/828 +f 828/829/829 838/839/839 839/840/840 +f 839/840/840 829/830/830 828/829/829 +f 440/440/440 22/22/22 442/442/442 +f 442/442/442 830/831/831 440/440/440 +f 830/831/831 442/442/442 444/444/444 +f 444/444/444 831/832/832 830/831/831 +f 831/832/832 444/444/444 446/446/446 +f 446/446/446 832/833/833 831/832/832 +f 832/833/833 446/446/446 448/448/448 +f 448/448/448 833/834/834 832/833/833 +f 833/834/834 448/448/448 450/450/450 +f 450/450/450 834/835/835 833/834/834 +f 834/835/835 450/450/450 452/452/452 +f 452/452/452 835/836/836 834/835/835 +f 835/836/836 452/452/452 454/454/454 +f 454/454/454 836/837/837 835/836/836 +f 836/837/837 454/454/454 456/456/456 +f 456/456/456 837/838/838 836/837/837 +f 837/838/838 456/456/456 458/458/458 +f 458/458/458 838/839/839 837/838/838 +f 838/839/839 458/458/458 460/460/460 +f 460/460/460 839/840/840 838/839/839 +f 460/460/460 459/459/459 840/841/841 +f 840/841/841 841/842/842 460/460/460 +f 841/842/842 840/841/841 842/843/843 +f 842/843/843 843/844/844 841/842/842 +f 843/844/844 842/843/843 844/845/845 +f 844/845/845 845/846/846 843/844/844 +f 845/846/846 844/845/845 846/847/847 +f 846/847/847 847/848/848 845/846/846 +f 847/848/848 846/847/847 848/849/849 +f 848/849/849 849/850/850 847/848/848 +f 849/850/850 848/849/849 850/851/851 +f 850/851/851 851/852/852 849/850/850 +f 851/852/852 850/851/851 852/853/853 +f 852/853/853 853/854/854 851/852/852 +f 853/854/854 852/853/853 854/855/855 +f 854/855/855 855/856/856 853/854/854 +f 855/856/856 854/855/855 856/857/857 +f 856/857/857 857/858/858 855/856/856 +f 857/858/858 856/857/857 858/21/859 +f 858/21/859 859/22/860 857/858/858 +f 459/459/459 470/470/470 860/859/861 +f 860/859/861 840/841/841 459/459/459 +f 840/841/841 860/859/861 861/860/862 +f 861/860/862 842/843/843 840/841/841 +f 842/843/843 861/860/862 862/861/863 +f 862/861/863 844/845/845 842/843/843 +f 844/845/845 862/861/863 863/862/864 +f 863/862/864 846/847/847 844/845/845 +f 846/847/847 863/862/864 864/863/865 +f 864/863/865 848/849/849 846/847/847 +f 848/849/849 864/863/865 865/864/866 +f 865/864/866 850/851/851 848/849/849 +f 850/851/851 865/864/866 866/865/867 +f 866/865/867 852/853/853 850/851/851 +f 852/853/853 866/865/867 867/866/868 +f 867/866/868 854/855/855 852/853/853 +f 854/855/855 867/866/868 868/867/869 +f 868/867/869 856/857/857 854/855/855 +f 856/857/857 868/867/869 869/33/870 +f 869/33/870 858/21/859 856/857/857 +f 470/470/470 480/480/480 870/868/871 +f 870/868/871 860/859/861 470/470/470 +f 860/859/861 870/868/871 871/869/872 +f 871/869/872 861/860/862 860/859/861 +f 861/860/862 871/869/872 872/870/873 +f 872/870/873 862/861/863 861/860/862 +f 862/861/863 872/870/873 873/871/874 +f 873/871/874 863/862/864 862/861/863 +f 863/862/864 873/871/874 874/872/875 +f 874/872/875 864/863/865 863/862/864 +f 864/863/865 874/872/875 875/873/876 +f 875/873/876 865/864/866 864/863/865 +f 865/864/866 875/873/876 876/874/877 +f 876/874/877 866/865/867 865/864/866 +f 866/865/867 876/874/877 877/875/878 +f 877/875/878 867/866/868 866/865/867 +f 867/866/868 877/875/878 878/876/879 +f 878/876/879 868/867/869 867/866/868 +f 868/867/869 878/876/879 879/44/880 +f 879/44/880 869/33/870 868/867/869 +f 480/480/480 490/490/490 880/877/881 +f 880/877/881 870/868/871 480/480/480 +f 870/868/871 880/877/881 881/878/882 +f 881/878/882 871/869/872 870/868/871 +f 871/869/872 881/878/882 882/879/883 +f 882/879/883 872/870/873 871/869/872 +f 872/870/873 882/879/883 883/880/884 +f 883/880/884 873/871/874 872/870/873 +f 873/871/874 883/880/884 884/881/885 +f 884/881/885 874/872/875 873/871/874 +f 874/872/875 884/881/885 885/882/886 +f 885/882/886 875/873/876 874/872/875 +f 875/873/876 885/882/886 886/883/887 +f 886/883/887 876/874/877 875/873/876 +f 876/874/877 886/883/887 887/884/888 +f 887/884/888 877/875/878 876/874/877 +f 877/875/878 887/884/888 888/885/889 +f 888/885/889 878/876/879 877/875/878 +f 878/876/879 888/885/889 889/55/890 +f 889/55/890 879/44/880 878/876/879 +f 490/490/490 500/500/500 890/886/891 +f 890/886/891 880/877/881 490/490/490 +f 880/877/881 890/886/891 891/887/892 +f 891/887/892 881/878/882 880/877/881 +f 881/878/882 891/887/892 892/888/893 +f 892/888/893 882/879/883 881/878/882 +f 882/879/883 892/888/893 893/889/894 +f 893/889/894 883/880/884 882/879/883 +f 883/880/884 893/889/894 894/890/895 +f 894/890/895 884/881/885 883/880/884 +f 884/881/885 894/890/895 895/891/896 +f 895/891/896 885/882/886 884/881/885 +f 885/882/886 895/891/896 896/892/897 +f 896/892/897 886/883/887 885/882/886 +f 886/883/887 896/892/897 897/893/898 +f 897/893/898 887/884/888 886/883/887 +f 887/884/888 897/893/898 898/894/899 +f 898/894/899 888/885/889 887/884/888 +f 888/885/889 898/894/899 899/66/900 +f 899/66/900 889/55/890 888/885/889 +f 500/500/500 510/510/510 900/895/901 +f 900/895/901 890/886/891 500/500/500 +f 890/886/891 900/895/901 901/896/902 +f 901/896/902 891/887/892 890/886/891 +f 891/887/892 901/896/902 902/897/903 +f 902/897/903 892/888/893 891/887/892 +f 892/888/893 902/897/903 903/898/904 +f 903/898/904 893/889/894 892/888/893 +f 893/889/894 903/898/904 904/899/905 +f 904/899/905 894/890/895 893/889/894 +f 894/890/895 904/899/905 905/900/906 +f 905/900/906 895/891/896 894/890/895 +f 895/891/896 905/900/906 906/901/907 +f 906/901/907 896/892/897 895/891/896 +f 896/892/897 906/901/907 907/902/908 +f 907/902/908 897/893/898 896/892/897 +f 897/893/898 907/902/908 908/903/909 +f 908/903/909 898/894/899 897/893/898 +f 898/894/899 908/903/909 909/77/910 +f 909/77/910 899/66/900 898/894/899 +f 510/510/510 520/520/520 910/904/911 +f 910/904/911 900/895/901 510/510/510 +f 900/895/901 910/904/911 911/905/912 +f 911/905/912 901/896/902 900/895/901 +f 901/896/902 911/905/912 912/906/913 +f 912/906/913 902/897/903 901/896/902 +f 902/897/903 912/906/913 913/907/914 +f 913/907/914 903/898/904 902/897/903 +f 903/898/904 913/907/914 914/908/915 +f 914/908/915 904/899/905 903/898/904 +f 904/899/905 914/908/915 915/909/916 +f 915/909/916 905/900/906 904/899/905 +f 905/900/906 915/909/916 916/910/917 +f 916/910/917 906/901/907 905/900/906 +f 906/901/907 916/910/917 917/911/918 +f 917/911/918 907/902/908 906/901/907 +f 907/902/908 917/911/918 918/912/919 +f 918/912/919 908/903/909 907/902/908 +f 908/903/909 918/912/919 919/88/920 +f 919/88/920 909/77/910 908/903/909 +f 520/520/520 530/530/530 920/913/921 +f 920/913/921 910/904/911 520/520/520 +f 910/904/911 920/913/921 921/914/922 +f 921/914/922 911/905/912 910/904/911 +f 911/905/912 921/914/922 922/915/923 +f 922/915/923 912/906/913 911/905/912 +f 912/906/913 922/915/923 923/916/924 +f 923/916/924 913/907/914 912/906/913 +f 913/907/914 923/916/924 924/917/925 +f 924/917/925 914/908/915 913/907/914 +f 914/908/915 924/917/925 925/918/926 +f 925/918/926 915/909/916 914/908/915 +f 915/909/916 925/918/926 926/919/927 +f 926/919/927 916/910/917 915/909/916 +f 916/910/917 926/919/927 927/920/928 +f 927/920/928 917/911/918 916/910/917 +f 917/911/918 927/920/928 928/921/929 +f 928/921/929 918/912/919 917/911/918 +f 918/912/919 928/921/929 929/99/930 +f 929/99/930 919/88/920 918/912/919 +f 530/530/530 540/540/540 930/922/931 +f 930/922/931 920/913/921 530/530/530 +f 920/913/921 930/922/931 931/923/932 +f 931/923/932 921/914/922 920/913/921 +f 921/914/922 931/923/932 932/924/933 +f 932/924/933 922/915/923 921/914/922 +f 922/915/923 932/924/933 933/925/934 +f 933/925/934 923/916/924 922/915/923 +f 923/916/924 933/925/934 934/926/935 +f 934/926/935 924/917/925 923/916/924 +f 924/917/925 934/926/935 935/927/936 +f 935/927/936 925/918/926 924/917/925 +f 925/918/926 935/927/936 936/928/937 +f 936/928/937 926/919/927 925/918/926 +f 926/919/927 936/928/937 937/929/938 +f 937/929/938 927/920/928 926/919/927 +f 927/920/928 937/929/938 938/930/939 +f 938/930/939 928/921/929 927/920/928 +f 928/921/929 938/930/939 939/110/940 +f 939/110/940 929/99/930 928/921/929 +f 540/540/540 550/550/550 940/931/941 +f 940/931/941 930/922/931 540/540/540 +f 930/922/931 940/931/941 941/932/942 +f 941/932/942 931/923/932 930/922/931 +f 931/923/932 941/932/942 942/933/943 +f 942/933/943 932/924/933 931/923/932 +f 932/924/933 942/933/943 943/934/944 +f 943/934/944 933/925/934 932/924/933 +f 933/925/934 943/934/944 944/935/945 +f 944/935/945 934/926/935 933/925/934 +f 934/926/935 944/935/945 945/936/946 +f 945/936/946 935/927/936 934/926/935 +f 935/927/936 945/936/946 946/937/947 +f 946/937/947 936/928/937 935/927/936 +f 936/928/937 946/937/947 947/938/948 +f 947/938/948 937/929/938 936/928/937 +f 937/929/938 947/938/948 948/939/949 +f 948/939/949 938/930/939 937/929/938 +f 938/930/939 948/939/949 949/121/950 +f 949/121/950 939/110/940 938/930/939 +f 550/550/550 560/560/560 950/940/951 +f 950/940/951 940/931/941 550/550/550 +f 940/931/941 950/940/951 951/941/952 +f 951/941/952 941/932/942 940/931/941 +f 941/932/942 951/941/952 952/942/953 +f 952/942/953 942/933/943 941/932/942 +f 942/933/943 952/942/953 953/943/954 +f 953/943/954 943/934/944 942/933/943 +f 943/934/944 953/943/954 954/944/955 +f 954/944/955 944/935/945 943/934/944 +f 944/935/945 954/944/955 955/945/956 +f 955/945/956 945/936/946 944/935/945 +f 945/936/946 955/945/956 956/946/957 +f 956/946/957 946/937/947 945/936/946 +f 946/937/947 956/946/957 957/947/958 +f 957/947/958 947/938/948 946/937/947 +f 947/938/948 957/947/958 958/948/959 +f 958/948/959 948/939/949 947/938/948 +f 948/939/949 958/948/959 959/132/960 +f 959/132/960 949/121/950 948/939/949 +f 560/560/560 570/570/570 960/949/961 +f 960/949/961 950/940/951 560/560/560 +f 950/940/951 960/949/961 961/950/962 +f 961/950/962 951/941/952 950/940/951 +f 951/941/952 961/950/962 962/951/963 +f 962/951/963 952/942/953 951/941/952 +f 952/942/953 962/951/963 963/952/964 +f 963/952/964 953/943/954 952/942/953 +f 953/943/954 963/952/964 964/953/965 +f 964/953/965 954/944/955 953/943/954 +f 954/944/955 964/953/965 965/954/966 +f 965/954/966 955/945/956 954/944/955 +f 955/945/956 965/954/966 966/955/967 +f 966/955/967 956/946/957 955/945/956 +f 956/946/957 966/955/967 967/956/968 +f 967/956/968 957/947/958 956/946/957 +f 957/947/958 967/956/968 968/957/969 +f 968/957/969 958/948/959 957/947/958 +f 958/948/959 968/957/969 969/143/970 +f 969/143/970 959/132/960 958/948/959 +f 570/570/570 580/580/580 970/958/971 +f 970/958/971 960/949/961 570/570/570 +f 960/949/961 970/958/971 971/959/972 +f 971/959/972 961/950/962 960/949/961 +f 961/950/962 971/959/972 972/960/973 +f 972/960/973 962/951/963 961/950/962 +f 962/951/963 972/960/973 973/961/974 +f 973/961/974 963/952/964 962/951/963 +f 963/952/964 973/961/974 974/962/975 +f 974/962/975 964/953/965 963/952/964 +f 964/953/965 974/962/975 975/963/976 +f 975/963/976 965/954/966 964/953/965 +f 965/954/966 975/963/976 976/964/977 +f 976/964/977 966/955/967 965/954/966 +f 966/955/967 976/964/977 977/965/978 +f 977/965/978 967/956/968 966/955/967 +f 967/956/968 977/965/978 978/966/979 +f 978/966/979 968/957/969 967/956/968 +f 968/957/969 978/966/979 979/154/980 +f 979/154/980 969/143/970 968/957/969 +f 580/580/580 590/590/590 980/967/981 +f 980/967/981 970/958/971 580/580/580 +f 970/958/971 980/967/981 981/968/982 +f 981/968/982 971/959/972 970/958/971 +f 971/959/972 981/968/982 982/969/983 +f 982/969/983 972/960/973 971/959/972 +f 972/960/973 982/969/983 983/970/984 +f 983/970/984 973/961/974 972/960/973 +f 973/961/974 983/970/984 984/971/985 +f 984/971/985 974/962/975 973/961/974 +f 974/962/975 984/971/985 985/972/986 +f 985/972/986 975/963/976 974/962/975 +f 975/963/976 985/972/986 986/973/987 +f 986/973/987 976/964/977 975/963/976 +f 976/964/977 986/973/987 987/974/988 +f 987/974/988 977/965/978 976/964/977 +f 977/965/978 987/974/988 988/975/989 +f 988/975/989 978/966/979 977/965/978 +f 978/966/979 988/975/989 989/165/990 +f 989/165/990 979/154/980 978/966/979 +f 590/590/590 600/600/600 990/976/991 +f 990/976/991 980/967/981 590/590/590 +f 980/967/981 990/976/991 991/977/992 +f 991/977/992 981/968/982 980/967/981 +f 981/968/982 991/977/992 992/978/993 +f 992/978/993 982/969/983 981/968/982 +f 982/969/983 992/978/993 993/979/994 +f 993/979/994 983/970/984 982/969/983 +f 983/970/984 993/979/994 994/980/995 +f 994/980/995 984/971/985 983/970/984 +f 984/971/985 994/980/995 995/981/996 +f 995/981/996 985/972/986 984/971/985 +f 985/972/986 995/981/996 996/982/997 +f 996/982/997 986/973/987 985/972/986 +f 986/973/987 996/982/997 997/983/998 +f 997/983/998 987/974/988 986/973/987 +f 987/974/988 997/983/998 998/984/999 +f 998/984/999 988/975/989 987/974/988 +f 988/975/989 998/984/999 999/176/1000 +f 999/176/1000 989/165/990 988/975/989 +f 600/600/600 610/610/610 1000/985/1001 +f 1000/985/1001 990/976/991 600/600/600 +f 990/976/991 1000/985/1001 1001/986/1002 +f 1001/986/1002 991/977/992 990/976/991 +f 991/977/992 1001/986/1002 1002/987/1003 +f 1002/987/1003 992/978/993 991/977/992 +f 992/978/993 1002/987/1003 1003/988/1004 +f 1003/988/1004 993/979/994 992/978/993 +f 993/979/994 1003/988/1004 1004/989/1005 +f 1004/989/1005 994/980/995 993/979/994 +f 994/980/995 1004/989/1005 1005/990/1006 +f 1005/990/1006 995/981/996 994/980/995 +f 995/981/996 1005/990/1006 1006/991/1007 +f 1006/991/1007 996/982/997 995/981/996 +f 996/982/997 1006/991/1007 1007/992/1008 +f 1007/992/1008 997/983/998 996/982/997 +f 997/983/998 1007/992/1008 1008/993/1009 +f 1008/993/1009 998/984/999 997/983/998 +f 998/984/999 1008/993/1009 1009/187/1010 +f 1009/187/1010 999/176/1000 998/984/999 +f 610/610/610 620/620/620 1010/994/1011 +f 1010/994/1011 1000/985/1001 610/610/610 +f 1000/985/1001 1010/994/1011 1011/995/1012 +f 1011/995/1012 1001/986/1002 1000/985/1001 +f 1001/986/1002 1011/995/1012 1012/996/1013 +f 1012/996/1013 1002/987/1003 1001/986/1002 +f 1002/987/1003 1012/996/1013 1013/997/1014 +f 1013/997/1014 1003/988/1004 1002/987/1003 +f 1003/988/1004 1013/997/1014 1014/998/1015 +f 1014/998/1015 1004/989/1005 1003/988/1004 +f 1004/989/1005 1014/998/1015 1015/999/1016 +f 1015/999/1016 1005/990/1006 1004/989/1005 +f 1005/990/1006 1015/999/1016 1016/1000/1017 +f 1016/1000/1017 1006/991/1007 1005/990/1006 +f 1006/991/1007 1016/1000/1017 1017/1001/1018 +f 1017/1001/1018 1007/992/1008 1006/991/1007 +f 1007/992/1008 1017/1001/1018 1018/1002/1019 +f 1018/1002/1019 1008/993/1009 1007/992/1008 +f 1008/993/1009 1018/1002/1019 1019/198/1020 +f 1019/198/1020 1009/187/1010 1008/993/1009 +f 620/620/620 630/630/630 1020/1003/1021 +f 1020/1003/1021 1010/994/1011 620/620/620 +f 1010/994/1011 1020/1003/1021 1021/1004/1022 +f 1021/1004/1022 1011/995/1012 1010/994/1011 +f 1011/995/1012 1021/1004/1022 1022/1005/1023 +f 1022/1005/1023 1012/996/1013 1011/995/1012 +f 1012/996/1013 1022/1005/1023 1023/1006/1024 +f 1023/1006/1024 1013/997/1014 1012/996/1013 +f 1013/997/1014 1023/1006/1024 1024/1007/1025 +f 1024/1007/1025 1014/998/1015 1013/997/1014 +f 1014/998/1015 1024/1007/1025 1025/1008/1026 +f 1025/1008/1026 1015/999/1016 1014/998/1015 +f 1015/999/1016 1025/1008/1026 1026/1009/1027 +f 1026/1009/1027 1016/1000/1017 1015/999/1016 +f 1016/1000/1017 1026/1009/1027 1027/1010/1028 +f 1027/1010/1028 1017/1001/1018 1016/1000/1017 +f 1017/1001/1018 1027/1010/1028 1028/1011/1029 +f 1028/1011/1029 1018/1002/1019 1017/1001/1018 +f 1018/1002/1019 1028/1011/1029 1029/209/1030 +f 1029/209/1030 1019/198/1020 1018/1002/1019 +f 630/630/630 640/640/640 1030/1012/1031 +f 1030/1012/1031 1020/1003/1021 630/630/630 +f 1020/1003/1021 1030/1012/1031 1031/1013/1032 +f 1031/1013/1032 1021/1004/1022 1020/1003/1021 +f 1021/1004/1022 1031/1013/1032 1032/1014/1033 +f 1032/1014/1033 1022/1005/1023 1021/1004/1022 +f 1022/1005/1023 1032/1014/1033 1033/1015/1034 +f 1033/1015/1034 1023/1006/1024 1022/1005/1023 +f 1023/1006/1024 1033/1015/1034 1034/1016/1035 +f 1034/1016/1035 1024/1007/1025 1023/1006/1024 +f 1024/1007/1025 1034/1016/1035 1035/1017/1036 +f 1035/1017/1036 1025/1008/1026 1024/1007/1025 +f 1025/1008/1026 1035/1017/1036 1036/1018/1037 +f 1036/1018/1037 1026/1009/1027 1025/1008/1026 +f 1026/1009/1027 1036/1018/1037 1037/1019/1038 +f 1037/1019/1038 1027/1010/1028 1026/1009/1027 +f 1027/1010/1028 1037/1019/1038 1038/1020/1039 +f 1038/1020/1039 1028/1011/1029 1027/1010/1028 +f 1028/1011/1029 1038/1020/1039 1039/220/1040 +f 1039/220/1040 1029/209/1030 1028/1011/1029 +f 640/640/640 1840/650/650 1040/1021/1041 +f 1040/1021/1041 1030/1012/1031 640/640/640 +f 1030/1012/1031 1040/1021/1041 1041/1022/1042 +f 1041/1022/1042 1031/1013/1032 1030/1012/1031 +f 1031/1013/1032 1041/1022/1042 1042/1023/1043 +f 1042/1023/1043 1032/1014/1033 1031/1013/1032 +f 1032/1014/1033 1042/1023/1043 1043/1024/1044 +f 1043/1024/1044 1033/1015/1034 1032/1014/1033 +f 1033/1015/1034 1043/1024/1044 1044/1025/1045 +f 1044/1025/1045 1034/1016/1035 1033/1015/1034 +f 1034/1016/1035 1044/1025/1045 1045/1026/1046 +f 1045/1026/1046 1035/1017/1036 1034/1016/1035 +f 1035/1017/1036 1045/1026/1046 1046/1027/1047 +f 1046/1027/1047 1036/1018/1037 1035/1017/1036 +f 1036/1018/1037 1046/1027/1047 1047/1028/1048 +f 1047/1028/1048 1037/1019/1038 1036/1018/1037 +f 1037/1019/1038 1047/1028/1048 1048/1029/1049 +f 1048/1029/1049 1038/1020/1039 1037/1019/1038 +f 1038/1020/1039 1048/1029/1049 1049/231/1050 +f 1049/231/1050 1039/220/1040 1038/1020/1039 +f 1840/650/650 659/660/660 1050/1030/1051 +f 1050/1030/1051 1040/1021/1041 1840/650/650 +f 1040/1021/1041 1050/1030/1051 1051/1031/1052 +f 1051/1031/1052 1041/1022/1042 1040/1021/1041 +f 1041/1022/1042 1051/1031/1052 1052/1032/1053 +f 1052/1032/1053 1042/1023/1043 1041/1022/1042 +f 1042/1023/1043 1052/1032/1053 1053/1033/1054 +f 1053/1033/1054 1043/1024/1044 1042/1023/1043 +f 1043/1024/1044 1053/1033/1054 1054/1034/1055 +f 1054/1034/1055 1044/1025/1045 1043/1024/1044 +f 1044/1025/1045 1054/1034/1055 1055/1035/1056 +f 1055/1035/1056 1045/1026/1046 1044/1025/1045 +f 1045/1026/1046 1055/1035/1056 1056/1036/1057 +f 1056/1036/1057 1046/1027/1047 1045/1026/1046 +f 1046/1027/1047 1056/1036/1057 1057/1037/1058 +f 1057/1037/1058 1047/1028/1048 1046/1027/1047 +f 1047/1028/1048 1057/1037/1058 1058/1038/1059 +f 1058/1038/1059 1048/1029/1049 1047/1028/1048 +f 1048/1029/1049 1058/1038/1059 1059/242/1060 +f 1059/242/1060 1049/231/1050 1048/1029/1049 +f 659/660/660 669/670/670 1060/1039/1061 +f 1060/1039/1061 1050/1030/1051 659/660/660 +f 1050/1030/1051 1060/1039/1061 1061/1040/1062 +f 1061/1040/1062 1051/1031/1052 1050/1030/1051 +f 1051/1031/1052 1061/1040/1062 1062/1041/1063 +f 1062/1041/1063 1052/1032/1053 1051/1031/1052 +f 1052/1032/1053 1062/1041/1063 1063/1042/1064 +f 1063/1042/1064 1053/1033/1054 1052/1032/1053 +f 1053/1033/1054 1063/1042/1064 1064/1043/1065 +f 1064/1043/1065 1054/1034/1055 1053/1033/1054 +f 1054/1034/1055 1064/1043/1065 1065/1044/1066 +f 1065/1044/1066 1055/1035/1056 1054/1034/1055 +f 1055/1035/1056 1065/1044/1066 1066/1045/1067 +f 1066/1045/1067 1056/1036/1057 1055/1035/1056 +f 1056/1036/1057 1066/1045/1067 1067/1046/1068 +f 1067/1046/1068 1057/1037/1058 1056/1036/1057 +f 1057/1037/1058 1067/1046/1068 1068/1047/1069 +f 1068/1047/1069 1058/1038/1059 1057/1037/1058 +f 1058/1038/1059 1068/1047/1069 1069/253/1070 +f 1069/253/1070 1059/242/1060 1058/1038/1059 +f 669/670/670 679/680/680 1070/1048/1071 +f 1070/1048/1071 1060/1039/1061 669/670/670 +f 1060/1039/1061 1070/1048/1071 1071/1049/1072 +f 1071/1049/1072 1061/1040/1062 1060/1039/1061 +f 1061/1040/1062 1071/1049/1072 1072/1050/1073 +f 1072/1050/1073 1062/1041/1063 1061/1040/1062 +f 1062/1041/1063 1072/1050/1073 1073/1051/1074 +f 1073/1051/1074 1063/1042/1064 1062/1041/1063 +f 1063/1042/1064 1073/1051/1074 1074/1052/1075 +f 1074/1052/1075 1064/1043/1065 1063/1042/1064 +f 1064/1043/1065 1074/1052/1075 1075/1053/1076 +f 1075/1053/1076 1065/1044/1066 1064/1043/1065 +f 1065/1044/1066 1075/1053/1076 1076/1054/1077 +f 1076/1054/1077 1066/1045/1067 1065/1044/1066 +f 1066/1045/1067 1076/1054/1077 1077/1055/1078 +f 1077/1055/1078 1067/1046/1068 1066/1045/1067 +f 1067/1046/1068 1077/1055/1078 1078/1056/1079 +f 1078/1056/1079 1068/1047/1069 1067/1046/1068 +f 1068/1047/1069 1078/1056/1079 1079/264/1080 +f 1079/264/1080 1069/253/1070 1068/1047/1069 +f 679/680/680 689/690/690 1080/1057/1081 +f 1080/1057/1081 1070/1048/1071 679/680/680 +f 1070/1048/1071 1080/1057/1081 1081/1058/1082 +f 1081/1058/1082 1071/1049/1072 1070/1048/1071 +f 1071/1049/1072 1081/1058/1082 1082/1059/1083 +f 1082/1059/1083 1072/1050/1073 1071/1049/1072 +f 1072/1050/1073 1082/1059/1083 1083/1060/1084 +f 1083/1060/1084 1073/1051/1074 1072/1050/1073 +f 1073/1051/1074 1083/1060/1084 1084/1061/1085 +f 1084/1061/1085 1074/1052/1075 1073/1051/1074 +f 1074/1052/1075 1084/1061/1085 1085/1062/1086 +f 1085/1062/1086 1075/1053/1076 1074/1052/1075 +f 1075/1053/1076 1085/1062/1086 1086/1063/1087 +f 1086/1063/1087 1076/1054/1077 1075/1053/1076 +f 1076/1054/1077 1086/1063/1087 1087/1064/1088 +f 1087/1064/1088 1077/1055/1078 1076/1054/1077 +f 1077/1055/1078 1087/1064/1088 1088/1065/1089 +f 1088/1065/1089 1078/1056/1079 1077/1055/1078 +f 1078/1056/1079 1088/1065/1089 1089/275/1090 +f 1089/275/1090 1079/264/1080 1078/1056/1079 +f 689/690/690 699/700/700 1090/1066/1091 +f 1090/1066/1091 1080/1057/1081 689/690/690 +f 1080/1057/1081 1090/1066/1091 1091/1067/1092 +f 1091/1067/1092 1081/1058/1082 1080/1057/1081 +f 1081/1058/1082 1091/1067/1092 1092/1068/1093 +f 1092/1068/1093 1082/1059/1083 1081/1058/1082 +f 1082/1059/1083 1092/1068/1093 1093/1069/1094 +f 1093/1069/1094 1083/1060/1084 1082/1059/1083 +f 1083/1060/1084 1093/1069/1094 1094/1070/1095 +f 1094/1070/1095 1084/1061/1085 1083/1060/1084 +f 1084/1061/1085 1094/1070/1095 1095/1071/1096 +f 1095/1071/1096 1085/1062/1086 1084/1061/1085 +f 1085/1062/1086 1095/1071/1096 1096/1072/1097 +f 1096/1072/1097 1086/1063/1087 1085/1062/1086 +f 1086/1063/1087 1096/1072/1097 1097/1073/1098 +f 1097/1073/1098 1087/1064/1088 1086/1063/1087 +f 1087/1064/1088 1097/1073/1098 1098/1074/1099 +f 1098/1074/1099 1088/1065/1089 1087/1064/1088 +f 1088/1065/1089 1098/1074/1099 1099/286/1100 +f 1099/286/1100 1089/275/1090 1088/1065/1089 +f 699/700/700 709/710/710 1100/1075/1101 +f 1100/1075/1101 1090/1066/1091 699/700/700 +f 1090/1066/1091 1100/1075/1101 1101/1076/1102 +f 1101/1076/1102 1091/1067/1092 1090/1066/1091 +f 1091/1067/1092 1101/1076/1102 1102/1077/1103 +f 1102/1077/1103 1092/1068/1093 1091/1067/1092 +f 1092/1068/1093 1102/1077/1103 1103/1078/1104 +f 1103/1078/1104 1093/1069/1094 1092/1068/1093 +f 1093/1069/1094 1103/1078/1104 1104/1079/1105 +f 1104/1079/1105 1094/1070/1095 1093/1069/1094 +f 1094/1070/1095 1104/1079/1105 1105/1080/1106 +f 1105/1080/1106 1095/1071/1096 1094/1070/1095 +f 1095/1071/1096 1105/1080/1106 1106/1081/1107 +f 1106/1081/1107 1096/1072/1097 1095/1071/1096 +f 1096/1072/1097 1106/1081/1107 1107/1082/1108 +f 1107/1082/1108 1097/1073/1098 1096/1072/1097 +f 1097/1073/1098 1107/1082/1108 1108/1083/1109 +f 1108/1083/1109 1098/1074/1099 1097/1073/1098 +f 1098/1074/1099 1108/1083/1109 1109/297/1110 +f 1109/297/1110 1099/286/1100 1098/1074/1099 +f 709/710/710 719/720/720 1110/1084/1111 +f 1110/1084/1111 1100/1075/1101 709/710/710 +f 1100/1075/1101 1110/1084/1111 1111/1085/1112 +f 1111/1085/1112 1101/1076/1102 1100/1075/1101 +f 1101/1076/1102 1111/1085/1112 1112/1086/1113 +f 1112/1086/1113 1102/1077/1103 1101/1076/1102 +f 1102/1077/1103 1112/1086/1113 1113/1087/1114 +f 1113/1087/1114 1103/1078/1104 1102/1077/1103 +f 1103/1078/1104 1113/1087/1114 1114/1088/1115 +f 1114/1088/1115 1104/1079/1105 1103/1078/1104 +f 1104/1079/1105 1114/1088/1115 1115/1089/1116 +f 1115/1089/1116 1105/1080/1106 1104/1079/1105 +f 1105/1080/1106 1115/1089/1116 1116/1090/1117 +f 1116/1090/1117 1106/1081/1107 1105/1080/1106 +f 1106/1081/1107 1116/1090/1117 1117/1091/1118 +f 1117/1091/1118 1107/1082/1108 1106/1081/1107 +f 1107/1082/1108 1117/1091/1118 1118/1092/1119 +f 1118/1092/1119 1108/1083/1109 1107/1082/1108 +f 1108/1083/1109 1118/1092/1119 1119/308/1120 +f 1119/308/1120 1109/297/1110 1108/1083/1109 +f 719/720/720 729/730/730 1120/1093/1121 +f 1120/1093/1121 1110/1084/1111 719/720/720 +f 1110/1084/1111 1120/1093/1121 1121/1094/1122 +f 1121/1094/1122 1111/1085/1112 1110/1084/1111 +f 1111/1085/1112 1121/1094/1122 1122/1095/1123 +f 1122/1095/1123 1112/1086/1113 1111/1085/1112 +f 1112/1086/1113 1122/1095/1123 1123/1096/1124 +f 1123/1096/1124 1113/1087/1114 1112/1086/1113 +f 1113/1087/1114 1123/1096/1124 1124/1097/1125 +f 1124/1097/1125 1114/1088/1115 1113/1087/1114 +f 1114/1088/1115 1124/1097/1125 1125/1098/1126 +f 1125/1098/1126 1115/1089/1116 1114/1088/1115 +f 1115/1089/1116 1125/1098/1126 1126/1099/1127 +f 1126/1099/1127 1116/1090/1117 1115/1089/1116 +f 1116/1090/1117 1126/1099/1127 1127/1100/1128 +f 1127/1100/1128 1117/1091/1118 1116/1090/1117 +f 1117/1091/1118 1127/1100/1128 1128/1101/1129 +f 1128/1101/1129 1118/1092/1119 1117/1091/1118 +f 1118/1092/1119 1128/1101/1129 1129/319/1130 +f 1129/319/1130 1119/308/1120 1118/1092/1119 +f 729/730/730 739/740/740 1130/1102/1131 +f 1130/1102/1131 1120/1093/1121 729/730/730 +f 1120/1093/1121 1130/1102/1131 1131/1103/1132 +f 1131/1103/1132 1121/1094/1122 1120/1093/1121 +f 1121/1094/1122 1131/1103/1132 1132/1104/1133 +f 1132/1104/1133 1122/1095/1123 1121/1094/1122 +f 1122/1095/1123 1132/1104/1133 1133/1105/1134 +f 1133/1105/1134 1123/1096/1124 1122/1095/1123 +f 1123/1096/1124 1133/1105/1134 1134/1106/1135 +f 1134/1106/1135 1124/1097/1125 1123/1096/1124 +f 1124/1097/1125 1134/1106/1135 1135/1107/1136 +f 1135/1107/1136 1125/1098/1126 1124/1097/1125 +f 1125/1098/1126 1135/1107/1136 1136/1108/1137 +f 1136/1108/1137 1126/1099/1127 1125/1098/1126 +f 1126/1099/1127 1136/1108/1137 1137/1109/1138 +f 1137/1109/1138 1127/1100/1128 1126/1099/1127 +f 1127/1100/1128 1137/1109/1138 1138/1110/1139 +f 1138/1110/1139 1128/1101/1129 1127/1100/1128 +f 1128/1101/1129 1138/1110/1139 1139/330/1140 +f 1139/330/1140 1129/319/1130 1128/1101/1129 +f 739/740/740 749/750/750 1140/1111/1141 +f 1140/1111/1141 1130/1102/1131 739/740/740 +f 1130/1102/1131 1140/1111/1141 1141/1112/1142 +f 1141/1112/1142 1131/1103/1132 1130/1102/1131 +f 1131/1103/1132 1141/1112/1142 1142/1113/1143 +f 1142/1113/1143 1132/1104/1133 1131/1103/1132 +f 1132/1104/1133 1142/1113/1143 1143/1114/1144 +f 1143/1114/1144 1133/1105/1134 1132/1104/1133 +f 1133/1105/1134 1143/1114/1144 1144/1115/1145 +f 1144/1115/1145 1134/1106/1135 1133/1105/1134 +f 1134/1106/1135 1144/1115/1145 1145/1116/1146 +f 1145/1116/1146 1135/1107/1136 1134/1106/1135 +f 1135/1107/1136 1145/1116/1146 1146/1117/1147 +f 1146/1117/1147 1136/1108/1137 1135/1107/1136 +f 1136/1108/1137 1146/1117/1147 1147/1118/1148 +f 1147/1118/1148 1137/1109/1138 1136/1108/1137 +f 1137/1109/1138 1147/1118/1148 1148/1119/1149 +f 1148/1119/1149 1138/1110/1139 1137/1109/1138 +f 1138/1110/1139 1148/1119/1149 1149/341/1150 +f 1149/341/1150 1139/330/1140 1138/1110/1139 +f 749/750/750 759/760/760 1150/1120/1151 +f 1150/1120/1151 1140/1111/1141 749/750/750 +f 1140/1111/1141 1150/1120/1151 1151/1121/1152 +f 1151/1121/1152 1141/1112/1142 1140/1111/1141 +f 1141/1112/1142 1151/1121/1152 1152/1122/1153 +f 1152/1122/1153 1142/1113/1143 1141/1112/1142 +f 1142/1113/1143 1152/1122/1153 1153/1123/1154 +f 1153/1123/1154 1143/1114/1144 1142/1113/1143 +f 1143/1114/1144 1153/1123/1154 1154/1124/1155 +f 1154/1124/1155 1144/1115/1145 1143/1114/1144 +f 1144/1115/1145 1154/1124/1155 1155/1125/1156 +f 1155/1125/1156 1145/1116/1146 1144/1115/1145 +f 1145/1116/1146 1155/1125/1156 1156/1126/1157 +f 1156/1126/1157 1146/1117/1147 1145/1116/1146 +f 1146/1117/1147 1156/1126/1157 1157/1127/1158 +f 1157/1127/1158 1147/1118/1148 1146/1117/1147 +f 1147/1118/1148 1157/1127/1158 1158/1128/1159 +f 1158/1128/1159 1148/1119/1149 1147/1118/1148 +f 1148/1119/1149 1158/1128/1159 1159/352/1160 +f 1159/352/1160 1149/341/1150 1148/1119/1149 +f 759/760/760 769/770/770 1160/1129/1161 +f 1160/1129/1161 1150/1120/1151 759/760/760 +f 1150/1120/1151 1160/1129/1161 1161/1130/1162 +f 1161/1130/1162 1151/1121/1152 1150/1120/1151 +f 1151/1121/1152 1161/1130/1162 1162/1131/1163 +f 1162/1131/1163 1152/1122/1153 1151/1121/1152 +f 1152/1122/1153 1162/1131/1163 1163/1132/1164 +f 1163/1132/1164 1153/1123/1154 1152/1122/1153 +f 1153/1123/1154 1163/1132/1164 1164/1133/1165 +f 1164/1133/1165 1154/1124/1155 1153/1123/1154 +f 1154/1124/1155 1164/1133/1165 1165/1134/1166 +f 1165/1134/1166 1155/1125/1156 1154/1124/1155 +f 1155/1125/1156 1165/1134/1166 1166/1135/1167 +f 1166/1135/1167 1156/1126/1157 1155/1125/1156 +f 1156/1126/1157 1166/1135/1167 1167/1136/1168 +f 1167/1136/1168 1157/1127/1158 1156/1126/1157 +f 1157/1127/1158 1167/1136/1168 1168/1137/1169 +f 1168/1137/1169 1158/1128/1159 1157/1127/1158 +f 1158/1128/1159 1168/1137/1169 1169/363/1170 +f 1169/363/1170 1159/352/1160 1158/1128/1159 +f 769/770/770 779/780/780 1170/1138/1171 +f 1170/1138/1171 1160/1129/1161 769/770/770 +f 1160/1129/1161 1170/1138/1171 1171/1139/1172 +f 1171/1139/1172 1161/1130/1162 1160/1129/1161 +f 1161/1130/1162 1171/1139/1172 1172/1140/1173 +f 1172/1140/1173 1162/1131/1163 1161/1130/1162 +f 1162/1131/1163 1172/1140/1173 1173/1141/1174 +f 1173/1141/1174 1163/1132/1164 1162/1131/1163 +f 1163/1132/1164 1173/1141/1174 1174/1142/1175 +f 1174/1142/1175 1164/1133/1165 1163/1132/1164 +f 1164/1133/1165 1174/1142/1175 1175/1143/1176 +f 1175/1143/1176 1165/1134/1166 1164/1133/1165 +f 1165/1134/1166 1175/1143/1176 1176/1144/1177 +f 1176/1144/1177 1166/1135/1167 1165/1134/1166 +f 1166/1135/1167 1176/1144/1177 1177/1145/1178 +f 1177/1145/1178 1167/1136/1168 1166/1135/1167 +f 1167/1136/1168 1177/1145/1178 1178/1146/1179 +f 1178/1146/1179 1168/1137/1169 1167/1136/1168 +f 1168/1137/1169 1178/1146/1179 1179/374/1180 +f 1179/374/1180 1169/363/1170 1168/1137/1169 +f 779/780/780 789/790/790 1180/1147/1181 +f 1180/1147/1181 1170/1138/1171 779/780/780 +f 1170/1138/1171 1180/1147/1181 1181/1148/1182 +f 1181/1148/1182 1171/1139/1172 1170/1138/1171 +f 1171/1139/1172 1181/1148/1182 1182/1149/1183 +f 1182/1149/1183 1172/1140/1173 1171/1139/1172 +f 1172/1140/1173 1182/1149/1183 1183/1150/1184 +f 1183/1150/1184 1173/1141/1174 1172/1140/1173 +f 1173/1141/1174 1183/1150/1184 1184/1151/1185 +f 1184/1151/1185 1174/1142/1175 1173/1141/1174 +f 1174/1142/1175 1184/1151/1185 1185/1152/1186 +f 1185/1152/1186 1175/1143/1176 1174/1142/1175 +f 1175/1143/1176 1185/1152/1186 1186/1153/1187 +f 1186/1153/1187 1176/1144/1177 1175/1143/1176 +f 1176/1144/1177 1186/1153/1187 1187/1154/1188 +f 1187/1154/1188 1177/1145/1178 1176/1144/1177 +f 1177/1145/1178 1187/1154/1188 1188/1155/1189 +f 1188/1155/1189 1178/1146/1179 1177/1145/1178 +f 1178/1146/1179 1188/1155/1189 1189/385/1190 +f 1189/385/1190 1179/374/1180 1178/1146/1179 +f 789/790/790 799/800/800 1190/1156/1191 +f 1190/1156/1191 1180/1147/1181 789/790/790 +f 1180/1147/1181 1190/1156/1191 1191/1157/1192 +f 1191/1157/1192 1181/1148/1182 1180/1147/1181 +f 1181/1148/1182 1191/1157/1192 1192/1158/1193 +f 1192/1158/1193 1182/1149/1183 1181/1148/1182 +f 1182/1149/1183 1192/1158/1193 1193/1159/1194 +f 1193/1159/1194 1183/1150/1184 1182/1149/1183 +f 1183/1150/1184 1193/1159/1194 1194/1160/1195 +f 1194/1160/1195 1184/1151/1185 1183/1150/1184 +f 1184/1151/1185 1194/1160/1195 1195/1161/1196 +f 1195/1161/1196 1185/1152/1186 1184/1151/1185 +f 1185/1152/1186 1195/1161/1196 1196/1162/1197 +f 1196/1162/1197 1186/1153/1187 1185/1152/1186 +f 1186/1153/1187 1196/1162/1197 1197/1163/1198 +f 1197/1163/1198 1187/1154/1188 1186/1153/1187 +f 1187/1154/1188 1197/1163/1198 1198/1164/1199 +f 1198/1164/1199 1188/1155/1189 1187/1154/1188 +f 1188/1155/1189 1198/1164/1199 1199/396/1200 +f 1199/396/1200 1189/385/1190 1188/1155/1189 +f 799/800/800 809/810/810 1200/1165/1201 +f 1200/1165/1201 1190/1156/1191 799/800/800 +f 1190/1156/1191 1200/1165/1201 1201/1166/1202 +f 1201/1166/1202 1191/1157/1192 1190/1156/1191 +f 1191/1157/1192 1201/1166/1202 1202/1167/1203 +f 1202/1167/1203 1192/1158/1193 1191/1157/1192 +f 1192/1158/1193 1202/1167/1203 1203/1168/1204 +f 1203/1168/1204 1193/1159/1194 1192/1158/1193 +f 1193/1159/1194 1203/1168/1204 1204/1169/1205 +f 1204/1169/1205 1194/1160/1195 1193/1159/1194 +f 1194/1160/1195 1204/1169/1205 1205/1170/1206 +f 1205/1170/1206 1195/1161/1196 1194/1160/1195 +f 1195/1161/1196 1205/1170/1206 1206/1171/1207 +f 1206/1171/1207 1196/1162/1197 1195/1161/1196 +f 1196/1162/1197 1206/1171/1207 1207/1172/1208 +f 1207/1172/1208 1197/1163/1198 1196/1162/1197 +f 1197/1163/1198 1207/1172/1208 1208/1173/1209 +f 1208/1173/1209 1198/1164/1199 1197/1163/1198 +f 1198/1164/1199 1208/1173/1209 1209/407/1210 +f 1209/407/1210 1199/396/1200 1198/1164/1199 +f 809/810/810 819/820/820 1210/1174/1211 +f 1210/1174/1211 1200/1165/1201 809/810/810 +f 1200/1165/1201 1210/1174/1211 1211/1175/1212 +f 1211/1175/1212 1201/1166/1202 1200/1165/1201 +f 1201/1166/1202 1211/1175/1212 1212/1176/1213 +f 1212/1176/1213 1202/1167/1203 1201/1166/1202 +f 1202/1167/1203 1212/1176/1213 1213/1177/1214 +f 1213/1177/1214 1203/1168/1204 1202/1167/1203 +f 1203/1168/1204 1213/1177/1214 1214/1178/1215 +f 1214/1178/1215 1204/1169/1205 1203/1168/1204 +f 1204/1169/1205 1214/1178/1215 1215/1179/1216 +f 1215/1179/1216 1205/1170/1206 1204/1169/1205 +f 1205/1170/1206 1215/1179/1216 1216/1180/1217 +f 1216/1180/1217 1206/1171/1207 1205/1170/1206 +f 1206/1171/1207 1216/1180/1217 1217/1181/1218 +f 1217/1181/1218 1207/1172/1208 1206/1171/1207 +f 1207/1172/1208 1217/1181/1218 1218/1182/1219 +f 1218/1182/1219 1208/1173/1209 1207/1172/1208 +f 1208/1173/1209 1218/1182/1219 1219/418/1220 +f 1219/418/1220 1209/407/1210 1208/1173/1209 +f 819/820/820 829/830/830 1220/1183/1221 +f 1220/1183/1221 1210/1174/1211 819/820/820 +f 1210/1174/1211 1220/1183/1221 1221/1184/1222 +f 1221/1184/1222 1211/1175/1212 1210/1174/1211 +f 1211/1175/1212 1221/1184/1222 1222/1185/1223 +f 1222/1185/1223 1212/1176/1213 1211/1175/1212 +f 1212/1176/1213 1222/1185/1223 1223/1186/1224 +f 1223/1186/1224 1213/1177/1214 1212/1176/1213 +f 1213/1177/1214 1223/1186/1224 1224/1187/1225 +f 1224/1187/1225 1214/1178/1215 1213/1177/1214 +f 1214/1178/1215 1224/1187/1225 1225/1188/1226 +f 1225/1188/1226 1215/1179/1216 1214/1178/1215 +f 1215/1179/1216 1225/1188/1226 1226/1189/1227 +f 1226/1189/1227 1216/1180/1217 1215/1179/1216 +f 1216/1180/1217 1226/1189/1227 1227/1190/1228 +f 1227/1190/1228 1217/1181/1218 1216/1180/1217 +f 1217/1181/1218 1227/1190/1228 1228/1191/1229 +f 1228/1191/1229 1218/1182/1219 1217/1181/1218 +f 1218/1182/1219 1228/1191/1229 1229/429/1230 +f 1229/429/1230 1219/418/1220 1218/1182/1219 +f 829/830/830 839/840/840 1230/1192/1231 +f 1230/1192/1231 1220/1183/1221 829/830/830 +f 1220/1183/1221 1230/1192/1231 1231/1193/1232 +f 1231/1193/1232 1221/1184/1222 1220/1183/1221 +f 1221/1184/1222 1231/1193/1232 1232/1194/1233 +f 1232/1194/1233 1222/1185/1223 1221/1184/1222 +f 1222/1185/1223 1232/1194/1233 1233/1195/1234 +f 1233/1195/1234 1223/1186/1224 1222/1185/1223 +f 1223/1186/1224 1233/1195/1234 1234/1196/1235 +f 1234/1196/1235 1224/1187/1225 1223/1186/1224 +f 1224/1187/1225 1234/1196/1235 1235/1197/1236 +f 1235/1197/1236 1225/1188/1226 1224/1187/1225 +f 1225/1188/1226 1235/1197/1236 1236/1198/1237 +f 1236/1198/1237 1226/1189/1227 1225/1188/1226 +f 1226/1189/1227 1236/1198/1237 1237/1199/1238 +f 1237/1199/1238 1227/1190/1228 1226/1189/1227 +f 1227/1190/1228 1237/1199/1238 1238/1200/1239 +f 1238/1200/1239 1228/1191/1229 1227/1190/1228 +f 1228/1191/1229 1238/1200/1239 1239/440/1240 +f 1239/440/1240 1229/429/1230 1228/1191/1229 +f 839/840/840 460/460/460 841/842/842 +f 841/842/842 1230/1192/1231 839/840/840 +f 1230/1192/1231 841/842/842 843/844/844 +f 843/844/844 1231/1193/1232 1230/1192/1231 +f 1231/1193/1232 843/844/844 845/846/846 +f 845/846/846 1232/1194/1233 1231/1193/1232 +f 1232/1194/1233 845/846/846 847/848/848 +f 847/848/848 1233/1195/1234 1232/1194/1233 +f 1233/1195/1234 847/848/848 849/850/850 +f 849/850/850 1234/1196/1235 1233/1195/1234 +f 1234/1196/1235 849/850/850 851/852/852 +f 851/852/852 1235/1197/1236 1234/1196/1235 +f 1235/1197/1236 851/852/852 853/854/854 +f 853/854/854 1236/1198/1237 1235/1197/1236 +f 1236/1198/1237 853/854/854 855/856/856 +f 855/856/856 1237/1199/1238 1236/1198/1237 +f 1237/1199/1238 855/856/856 857/858/858 +f 857/858/858 1238/1200/1239 1237/1199/1238 +f 1238/1200/1239 857/858/858 859/22/860 +f 859/22/860 1239/440/1240 1238/1200/1239 +f 859/22/860 858/21/859 1240/1201/1241 +f 1240/1201/1241 1241/1202/1242 859/22/860 +f 1241/1202/1242 1240/1201/1241 1242/1203/1243 +f 1242/1203/1243 1243/1204/1244 1241/1202/1242 +f 1243/1204/1244 1242/1203/1243 1244/1205/1245 +f 1244/1205/1245 1245/1206/1246 1243/1204/1244 +f 1245/1206/1246 1244/1205/1245 1246/1207/1247 +f 1246/1207/1247 1247/1208/1248 1245/1206/1246 +f 1247/1208/1248 1246/1207/1247 1248/1209/1249 +f 1248/1209/1249 1249/1210/1250 1247/1208/1248 +f 1249/1210/1250 1248/1209/1249 1250/1211/1251 +f 1250/1211/1251 1251/1212/1252 1249/1210/1250 +f 1251/1212/1252 1250/1211/1251 1252/1213/1253 +f 1252/1213/1253 1253/1214/1254 1251/1212/1252 +f 1253/1214/1254 1252/1213/1253 1254/1215/1255 +f 1254/1215/1255 1255/1216/1256 1253/1214/1254 +f 1255/1216/1256 1254/1215/1255 1256/1217/1257 +f 1256/1217/1257 1257/1218/1258 1255/1216/1256 +f 1257/1218/1258 1256/1217/1257 1258/1219/1259 +f 858/21/859 869/33/870 1259/1220/1260 +f 1259/1220/1260 1240/1201/1241 858/21/859 +f 1240/1201/1241 1259/1220/1260 1260/1221/1261 +f 1260/1221/1261 1242/1203/1243 1240/1201/1241 +f 1242/1203/1243 1260/1221/1261 1261/1222/1262 +f 1261/1222/1262 1244/1205/1245 1242/1203/1243 +f 1244/1205/1245 1261/1222/1262 1262/1223/1263 +f 1262/1223/1263 1246/1207/1247 1244/1205/1245 +f 1246/1207/1247 1262/1223/1263 1263/1224/1264 +f 1263/1224/1264 1248/1209/1249 1246/1207/1247 +f 1248/1209/1249 1263/1224/1264 1264/1225/1265 +f 1264/1225/1265 1250/1211/1251 1248/1209/1249 +f 1250/1211/1251 1264/1225/1265 1265/1226/1266 +f 1265/1226/1266 1252/1213/1253 1250/1211/1251 +f 1252/1213/1253 1265/1226/1266 1266/1227/1267 +f 1266/1227/1267 1254/1215/1255 1252/1213/1253 +f 1254/1215/1255 1266/1227/1267 1267/1228/1268 +f 1267/1228/1268 1256/1217/1257 1254/1215/1255 +f 1256/1217/1257 1267/1228/1268 1258/1219/1259 +f 869/33/870 879/44/880 1268/1229/1269 +f 1268/1229/1269 1259/1220/1260 869/33/870 +f 1259/1220/1260 1268/1229/1269 1269/1230/1270 +f 1269/1230/1270 1260/1221/1261 1259/1220/1260 +f 1260/1221/1261 1269/1230/1270 1270/1231/1271 +f 1270/1231/1271 1261/1222/1262 1260/1221/1261 +f 1261/1222/1262 1270/1231/1271 1271/1232/1272 +f 1271/1232/1272 1262/1223/1263 1261/1222/1262 +f 1262/1223/1263 1271/1232/1272 1272/1233/1273 +f 1272/1233/1273 1263/1224/1264 1262/1223/1263 +f 1263/1224/1264 1272/1233/1273 1273/1234/1274 +f 1273/1234/1274 1264/1225/1265 1263/1224/1264 +f 1264/1225/1265 1273/1234/1274 1274/1235/1275 +f 1274/1235/1275 1265/1226/1266 1264/1225/1265 +f 1265/1226/1266 1274/1235/1275 1275/1236/1276 +f 1275/1236/1276 1266/1227/1267 1265/1226/1266 +f 1266/1227/1267 1275/1236/1276 1276/1237/1277 +f 1276/1237/1277 1267/1228/1268 1266/1227/1267 +f 1267/1228/1268 1276/1237/1277 1258/1219/1259 +f 879/44/880 889/55/890 1277/1238/1278 +f 1277/1238/1278 1268/1229/1269 879/44/880 +f 1268/1229/1269 1277/1238/1278 1278/1239/1279 +f 1278/1239/1279 1269/1230/1270 1268/1229/1269 +f 1269/1230/1270 1278/1239/1279 1279/1240/1280 +f 1279/1240/1280 1270/1231/1271 1269/1230/1270 +f 1270/1231/1271 1279/1240/1280 1280/1241/1281 +f 1280/1241/1281 1271/1232/1272 1270/1231/1271 +f 1271/1232/1272 1280/1241/1281 1281/1242/1282 +f 1281/1242/1282 1272/1233/1273 1271/1232/1272 +f 1272/1233/1273 1281/1242/1282 1282/1243/1283 +f 1282/1243/1283 1273/1234/1274 1272/1233/1273 +f 1273/1234/1274 1282/1243/1283 1283/1244/1284 +f 1283/1244/1284 1274/1235/1275 1273/1234/1274 +f 1274/1235/1275 1283/1244/1284 1284/1245/1285 +f 1284/1245/1285 1275/1236/1276 1274/1235/1275 +f 1275/1236/1276 1284/1245/1285 1285/1246/1286 +f 1285/1246/1286 1276/1237/1277 1275/1236/1276 +f 1276/1237/1277 1285/1246/1286 1258/1219/1259 +f 889/55/890 899/66/900 1286/1247/1287 +f 1286/1247/1287 1277/1238/1278 889/55/890 +f 1277/1238/1278 1286/1247/1287 1287/1248/1288 +f 1287/1248/1288 1278/1239/1279 1277/1238/1278 +f 1278/1239/1279 1287/1248/1288 1288/1249/1289 +f 1288/1249/1289 1279/1240/1280 1278/1239/1279 +f 1279/1240/1280 1288/1249/1289 1289/1250/1290 +f 1289/1250/1290 1280/1241/1281 1279/1240/1280 +f 1280/1241/1281 1289/1250/1290 1290/1251/1291 +f 1290/1251/1291 1281/1242/1282 1280/1241/1281 +f 1281/1242/1282 1290/1251/1291 1291/1252/1292 +f 1291/1252/1292 1282/1243/1283 1281/1242/1282 +f 1282/1243/1283 1291/1252/1292 1292/1253/1293 +f 1292/1253/1293 1283/1244/1284 1282/1243/1283 +f 1283/1244/1284 1292/1253/1293 1293/1254/1294 +f 1293/1254/1294 1284/1245/1285 1283/1244/1284 +f 1284/1245/1285 1293/1254/1294 1294/1255/1295 +f 1294/1255/1295 1285/1246/1286 1284/1245/1285 +f 1285/1246/1286 1294/1255/1295 1258/1219/1259 +f 899/66/900 909/77/910 1295/1256/1296 +f 1295/1256/1296 1286/1247/1287 899/66/900 +f 1286/1247/1287 1295/1256/1296 1296/1257/1297 +f 1296/1257/1297 1287/1248/1288 1286/1247/1287 +f 1287/1248/1288 1296/1257/1297 1297/1258/1298 +f 1297/1258/1298 1288/1249/1289 1287/1248/1288 +f 1288/1249/1289 1297/1258/1298 1298/1259/1299 +f 1298/1259/1299 1289/1250/1290 1288/1249/1289 +f 1289/1250/1290 1298/1259/1299 1299/1260/1300 +f 1299/1260/1300 1290/1251/1291 1289/1250/1290 +f 1290/1251/1291 1299/1260/1300 1300/1261/1301 +f 1300/1261/1301 1291/1252/1292 1290/1251/1291 +f 1291/1252/1292 1300/1261/1301 1301/1262/1302 +f 1301/1262/1302 1292/1253/1293 1291/1252/1292 +f 1292/1253/1293 1301/1262/1302 1302/1263/1303 +f 1302/1263/1303 1293/1254/1294 1292/1253/1293 +f 1293/1254/1294 1302/1263/1303 1303/1264/1304 +f 1303/1264/1304 1294/1255/1295 1293/1254/1294 +f 1294/1255/1295 1303/1264/1304 1258/1219/1259 +f 909/77/910 919/88/920 1304/1265/1305 +f 1304/1265/1305 1295/1256/1296 909/77/910 +f 1295/1256/1296 1304/1265/1305 1305/1266/1306 +f 1305/1266/1306 1296/1257/1297 1295/1256/1296 +f 1296/1257/1297 1305/1266/1306 1306/1267/1307 +f 1306/1267/1307 1297/1258/1298 1296/1257/1297 +f 1297/1258/1298 1306/1267/1307 1307/1268/1308 +f 1307/1268/1308 1298/1259/1299 1297/1258/1298 +f 1298/1259/1299 1307/1268/1308 1308/1269/1309 +f 1308/1269/1309 1299/1260/1300 1298/1259/1299 +f 1299/1260/1300 1308/1269/1309 1309/1270/1310 +f 1309/1270/1310 1300/1261/1301 1299/1260/1300 +f 1300/1261/1301 1309/1270/1310 1310/1271/1311 +f 1310/1271/1311 1301/1262/1302 1300/1261/1301 +f 1301/1262/1302 1310/1271/1311 1311/1272/1312 +f 1311/1272/1312 1302/1263/1303 1301/1262/1302 +f 1302/1263/1303 1311/1272/1312 1312/1273/1313 +f 1312/1273/1313 1303/1264/1304 1302/1263/1303 +f 1303/1264/1304 1312/1273/1313 1258/1219/1259 +f 919/88/920 929/99/930 1313/1274/1314 +f 1313/1274/1314 1304/1265/1305 919/88/920 +f 1304/1265/1305 1313/1274/1314 1314/1275/1315 +f 1314/1275/1315 1305/1266/1306 1304/1265/1305 +f 1305/1266/1306 1314/1275/1315 1315/1276/1316 +f 1315/1276/1316 1306/1267/1307 1305/1266/1306 +f 1306/1267/1307 1315/1276/1316 1316/1277/1317 +f 1316/1277/1317 1307/1268/1308 1306/1267/1307 +f 1307/1268/1308 1316/1277/1317 1317/1278/1318 +f 1317/1278/1318 1308/1269/1309 1307/1268/1308 +f 1308/1269/1309 1317/1278/1318 1318/1279/1319 +f 1318/1279/1319 1309/1270/1310 1308/1269/1309 +f 1309/1270/1310 1318/1279/1319 1319/1280/1320 +f 1319/1280/1320 1310/1271/1311 1309/1270/1310 +f 1310/1271/1311 1319/1280/1320 1320/1281/1321 +f 1320/1281/1321 1311/1272/1312 1310/1271/1311 +f 1311/1272/1312 1320/1281/1321 1321/1282/1322 +f 1321/1282/1322 1312/1273/1313 1311/1272/1312 +f 1312/1273/1313 1321/1282/1322 1258/1219/1259 +f 929/99/930 939/110/940 1322/1283/1323 +f 1322/1283/1323 1313/1274/1314 929/99/930 +f 1313/1274/1314 1322/1283/1323 1323/1284/1324 +f 1323/1284/1324 1314/1275/1315 1313/1274/1314 +f 1314/1275/1315 1323/1284/1324 1324/1285/1325 +f 1324/1285/1325 1315/1276/1316 1314/1275/1315 +f 1315/1276/1316 1324/1285/1325 1325/1286/1326 +f 1325/1286/1326 1316/1277/1317 1315/1276/1316 +f 1316/1277/1317 1325/1286/1326 1326/1287/1327 +f 1326/1287/1327 1317/1278/1318 1316/1277/1317 +f 1317/1278/1318 1326/1287/1327 1327/1288/1328 +f 1327/1288/1328 1318/1279/1319 1317/1278/1318 +f 1318/1279/1319 1327/1288/1328 1328/1289/1329 +f 1328/1289/1329 1319/1280/1320 1318/1279/1319 +f 1319/1280/1320 1328/1289/1329 1329/1290/1330 +f 1329/1290/1330 1320/1281/1321 1319/1280/1320 +f 1320/1281/1321 1329/1290/1330 1330/1291/1331 +f 1330/1291/1331 1321/1282/1322 1320/1281/1321 +f 1321/1282/1322 1330/1291/1331 1258/1219/1259 +f 939/110/940 949/121/950 1331/1292/1332 +f 1331/1292/1332 1322/1283/1323 939/110/940 +f 1322/1283/1323 1331/1292/1332 1332/1293/1333 +f 1332/1293/1333 1323/1284/1324 1322/1283/1323 +f 1323/1284/1324 1332/1293/1333 1333/1294/1334 +f 1333/1294/1334 1324/1285/1325 1323/1284/1324 +f 1324/1285/1325 1333/1294/1334 1334/1295/1335 +f 1334/1295/1335 1325/1286/1326 1324/1285/1325 +f 1325/1286/1326 1334/1295/1335 1335/1296/1336 +f 1335/1296/1336 1326/1287/1327 1325/1286/1326 +f 1326/1287/1327 1335/1296/1336 1336/1297/1337 +f 1336/1297/1337 1327/1288/1328 1326/1287/1327 +f 1327/1288/1328 1336/1297/1337 1337/1298/1338 +f 1337/1298/1338 1328/1289/1329 1327/1288/1328 +f 1328/1289/1329 1337/1298/1338 1338/1299/1339 +f 1338/1299/1339 1329/1290/1330 1328/1289/1329 +f 1329/1290/1330 1338/1299/1339 1339/1300/1340 +f 1339/1300/1340 1330/1291/1331 1329/1290/1330 +f 1330/1291/1331 1339/1300/1340 1258/1219/1259 +f 949/121/950 959/132/960 1340/1301/1341 +f 1340/1301/1341 1331/1292/1332 949/121/950 +f 1331/1292/1332 1340/1301/1341 1341/1302/1342 +f 1341/1302/1342 1332/1293/1333 1331/1292/1332 +f 1332/1293/1333 1341/1302/1342 1342/1303/1343 +f 1342/1303/1343 1333/1294/1334 1332/1293/1333 +f 1333/1294/1334 1342/1303/1343 1343/1304/1344 +f 1343/1304/1344 1334/1295/1335 1333/1294/1334 +f 1334/1295/1335 1343/1304/1344 1344/1305/1345 +f 1344/1305/1345 1335/1296/1336 1334/1295/1335 +f 1335/1296/1336 1344/1305/1345 1345/1306/1346 +f 1345/1306/1346 1336/1297/1337 1335/1296/1336 +f 1336/1297/1337 1345/1306/1346 1346/1307/1347 +f 1346/1307/1347 1337/1298/1338 1336/1297/1337 +f 1337/1298/1338 1346/1307/1347 1347/1308/1348 +f 1347/1308/1348 1338/1299/1339 1337/1298/1338 +f 1338/1299/1339 1347/1308/1348 1348/1309/1349 +f 1348/1309/1349 1339/1300/1340 1338/1299/1339 +f 1339/1300/1340 1348/1309/1349 1258/1219/1259 +f 959/132/960 969/143/970 1349/1310/1350 +f 1349/1310/1350 1340/1301/1341 959/132/960 +f 1340/1301/1341 1349/1310/1350 1350/1311/1351 +f 1350/1311/1351 1341/1302/1342 1340/1301/1341 +f 1341/1302/1342 1350/1311/1351 1351/1312/1352 +f 1351/1312/1352 1342/1303/1343 1341/1302/1342 +f 1342/1303/1343 1351/1312/1352 1352/1313/1353 +f 1352/1313/1353 1343/1304/1344 1342/1303/1343 +f 1343/1304/1344 1352/1313/1353 1353/1314/1354 +f 1353/1314/1354 1344/1305/1345 1343/1304/1344 +f 1344/1305/1345 1353/1314/1354 1354/1315/1355 +f 1354/1315/1355 1345/1306/1346 1344/1305/1345 +f 1345/1306/1346 1354/1315/1355 1355/1316/1356 +f 1355/1316/1356 1346/1307/1347 1345/1306/1346 +f 1346/1307/1347 1355/1316/1356 1356/1317/1357 +f 1356/1317/1357 1347/1308/1348 1346/1307/1347 +f 1347/1308/1348 1356/1317/1357 1357/1318/1358 +f 1357/1318/1358 1348/1309/1349 1347/1308/1348 +f 1348/1309/1349 1357/1318/1358 1258/1219/1259 +f 969/143/970 979/154/980 1358/1319/1359 +f 1358/1319/1359 1349/1310/1350 969/143/970 +f 1349/1310/1350 1358/1319/1359 1359/1320/1360 +f 1359/1320/1360 1350/1311/1351 1349/1310/1350 +f 1350/1311/1351 1359/1320/1360 1360/1321/1361 +f 1360/1321/1361 1351/1312/1352 1350/1311/1351 +f 1351/1312/1352 1360/1321/1361 1361/1322/1362 +f 1361/1322/1362 1352/1313/1353 1351/1312/1352 +f 1352/1313/1353 1361/1322/1362 1362/1323/1363 +f 1362/1323/1363 1353/1314/1354 1352/1313/1353 +f 1353/1314/1354 1362/1323/1363 1363/1324/1364 +f 1363/1324/1364 1354/1315/1355 1353/1314/1354 +f 1354/1315/1355 1363/1324/1364 1364/1325/1365 +f 1364/1325/1365 1355/1316/1356 1354/1315/1355 +f 1355/1316/1356 1364/1325/1365 1365/1326/1366 +f 1365/1326/1366 1356/1317/1357 1355/1316/1356 +f 1356/1317/1357 1365/1326/1366 1366/1327/1367 +f 1366/1327/1367 1357/1318/1358 1356/1317/1357 +f 1357/1318/1358 1366/1327/1367 1258/1219/1259 +f 979/154/980 989/165/990 1367/1328/1368 +f 1367/1328/1368 1358/1319/1359 979/154/980 +f 1358/1319/1359 1367/1328/1368 1368/1329/1369 +f 1368/1329/1369 1359/1320/1360 1358/1319/1359 +f 1359/1320/1360 1368/1329/1369 1369/1330/1370 +f 1369/1330/1370 1360/1321/1361 1359/1320/1360 +f 1360/1321/1361 1369/1330/1370 1370/1331/1371 +f 1370/1331/1371 1361/1322/1362 1360/1321/1361 +f 1361/1322/1362 1370/1331/1371 1371/1332/1372 +f 1371/1332/1372 1362/1323/1363 1361/1322/1362 +f 1362/1323/1363 1371/1332/1372 1372/1333/1373 +f 1372/1333/1373 1363/1324/1364 1362/1323/1363 +f 1363/1324/1364 1372/1333/1373 1373/1334/1374 +f 1373/1334/1374 1364/1325/1365 1363/1324/1364 +f 1364/1325/1365 1373/1334/1374 1374/1335/1375 +f 1374/1335/1375 1365/1326/1366 1364/1325/1365 +f 1365/1326/1366 1374/1335/1375 1375/1336/1376 +f 1375/1336/1376 1366/1327/1367 1365/1326/1366 +f 1366/1327/1367 1375/1336/1376 1258/1219/1259 +f 989/165/990 999/176/1000 1376/1337/1377 +f 1376/1337/1377 1367/1328/1368 989/165/990 +f 1367/1328/1368 1376/1337/1377 1377/1338/1378 +f 1377/1338/1378 1368/1329/1369 1367/1328/1368 +f 1368/1329/1369 1377/1338/1378 1378/1339/1379 +f 1378/1339/1379 1369/1330/1370 1368/1329/1369 +f 1369/1330/1370 1378/1339/1379 1379/1340/1380 +f 1379/1340/1380 1370/1331/1371 1369/1330/1370 +f 1370/1331/1371 1379/1340/1380 1380/1341/1381 +f 1380/1341/1381 1371/1332/1372 1370/1331/1371 +f 1371/1332/1372 1380/1341/1381 1381/1342/1382 +f 1381/1342/1382 1372/1333/1373 1371/1332/1372 +f 1372/1333/1373 1381/1342/1382 1382/1343/1383 +f 1382/1343/1383 1373/1334/1374 1372/1333/1373 +f 1373/1334/1374 1382/1343/1383 1383/1344/1384 +f 1383/1344/1384 1374/1335/1375 1373/1334/1374 +f 1374/1335/1375 1383/1344/1384 1384/1345/1385 +f 1384/1345/1385 1375/1336/1376 1374/1335/1375 +f 1375/1336/1376 1384/1345/1385 1258/1219/1259 +f 999/176/1000 1009/187/1010 1385/1346/1386 +f 1385/1346/1386 1376/1337/1377 999/176/1000 +f 1376/1337/1377 1385/1346/1386 1386/1347/1387 +f 1386/1347/1387 1377/1338/1378 1376/1337/1377 +f 1377/1338/1378 1386/1347/1387 1387/1348/1388 +f 1387/1348/1388 1378/1339/1379 1377/1338/1378 +f 1378/1339/1379 1387/1348/1388 1388/1349/1389 +f 1388/1349/1389 1379/1340/1380 1378/1339/1379 +f 1379/1340/1380 1388/1349/1389 1389/1350/1390 +f 1389/1350/1390 1380/1341/1381 1379/1340/1380 +f 1380/1341/1381 1389/1350/1390 1390/1351/1391 +f 1390/1351/1391 1381/1342/1382 1380/1341/1381 +f 1381/1342/1382 1390/1351/1391 1391/1352/1392 +f 1391/1352/1392 1382/1343/1383 1381/1342/1382 +f 1382/1343/1383 1391/1352/1392 1392/1353/1393 +f 1392/1353/1393 1383/1344/1384 1382/1343/1383 +f 1383/1344/1384 1392/1353/1393 1393/1354/1394 +f 1393/1354/1394 1384/1345/1385 1383/1344/1384 +f 1384/1345/1385 1393/1354/1394 1258/1219/1259 +f 1009/187/1010 1019/198/1020 1394/1355/1395 +f 1394/1355/1395 1385/1346/1386 1009/187/1010 +f 1385/1346/1386 1394/1355/1395 1395/1356/1396 +f 1395/1356/1396 1386/1347/1387 1385/1346/1386 +f 1386/1347/1387 1395/1356/1396 1396/1357/1397 +f 1396/1357/1397 1387/1348/1388 1386/1347/1387 +f 1387/1348/1388 1396/1357/1397 1397/1358/1398 +f 1397/1358/1398 1388/1349/1389 1387/1348/1388 +f 1388/1349/1389 1397/1358/1398 1398/1359/1399 +f 1398/1359/1399 1389/1350/1390 1388/1349/1389 +f 1389/1350/1390 1398/1359/1399 1399/1360/1400 +f 1399/1360/1400 1390/1351/1391 1389/1350/1390 +f 1390/1351/1391 1399/1360/1400 1400/1361/1401 +f 1400/1361/1401 1391/1352/1392 1390/1351/1391 +f 1391/1352/1392 1400/1361/1401 1401/1362/1402 +f 1401/1362/1402 1392/1353/1393 1391/1352/1392 +f 1392/1353/1393 1401/1362/1402 1402/1363/1403 +f 1402/1363/1403 1393/1354/1394 1392/1353/1393 +f 1393/1354/1394 1402/1363/1403 1258/1219/1259 +f 1019/198/1020 1029/209/1030 1403/1364/1404 +f 1403/1364/1404 1394/1355/1395 1019/198/1020 +f 1394/1355/1395 1403/1364/1404 1404/1365/1405 +f 1404/1365/1405 1395/1356/1396 1394/1355/1395 +f 1395/1356/1396 1404/1365/1405 1405/1366/1406 +f 1405/1366/1406 1396/1357/1397 1395/1356/1396 +f 1396/1357/1397 1405/1366/1406 1406/1367/1407 +f 1406/1367/1407 1397/1358/1398 1396/1357/1397 +f 1397/1358/1398 1406/1367/1407 1407/1368/1408 +f 1407/1368/1408 1398/1359/1399 1397/1358/1398 +f 1398/1359/1399 1407/1368/1408 1408/1369/1409 +f 1408/1369/1409 1399/1360/1400 1398/1359/1399 +f 1399/1360/1400 1408/1369/1409 1409/1370/1410 +f 1409/1370/1410 1400/1361/1401 1399/1360/1400 +f 1400/1361/1401 1409/1370/1410 1410/1371/1411 +f 1410/1371/1411 1401/1362/1402 1400/1361/1401 +f 1401/1362/1402 1410/1371/1411 1411/1372/1412 +f 1411/1372/1412 1402/1363/1403 1401/1362/1402 +f 1402/1363/1403 1411/1372/1412 1258/1219/1259 +f 1029/209/1030 1039/220/1040 1412/1373/1413 +f 1412/1373/1413 1403/1364/1404 1029/209/1030 +f 1403/1364/1404 1412/1373/1413 1413/1374/1414 +f 1413/1374/1414 1404/1365/1405 1403/1364/1404 +f 1404/1365/1405 1413/1374/1414 1414/1375/1415 +f 1414/1375/1415 1405/1366/1406 1404/1365/1405 +f 1405/1366/1406 1414/1375/1415 1415/1376/1416 +f 1415/1376/1416 1406/1367/1407 1405/1366/1406 +f 1406/1367/1407 1415/1376/1416 1416/1377/1417 +f 1416/1377/1417 1407/1368/1408 1406/1367/1407 +f 1407/1368/1408 1416/1377/1417 1417/1378/1418 +f 1417/1378/1418 1408/1369/1409 1407/1368/1408 +f 1408/1369/1409 1417/1378/1418 1418/1379/1419 +f 1418/1379/1419 1409/1370/1410 1408/1369/1409 +f 1409/1370/1410 1418/1379/1419 1419/1380/1420 +f 1419/1380/1420 1410/1371/1411 1409/1370/1410 +f 1410/1371/1411 1419/1380/1420 1420/1381/1421 +f 1420/1381/1421 1411/1372/1412 1410/1371/1411 +f 1411/1372/1412 1420/1381/1421 1258/1219/1259 +f 1039/220/1040 1049/231/1050 1421/1382/1422 +f 1421/1382/1422 1412/1373/1413 1039/220/1040 +f 1412/1373/1413 1421/1382/1422 1422/1383/1423 +f 1422/1383/1423 1413/1374/1414 1412/1373/1413 +f 1413/1374/1414 1422/1383/1423 1423/1384/1424 +f 1423/1384/1424 1414/1375/1415 1413/1374/1414 +f 1414/1375/1415 1423/1384/1424 1424/1385/1425 +f 1424/1385/1425 1415/1376/1416 1414/1375/1415 +f 1415/1376/1416 1424/1385/1425 1425/1386/1426 +f 1425/1386/1426 1416/1377/1417 1415/1376/1416 +f 1416/1377/1417 1425/1386/1426 1426/1387/1427 +f 1426/1387/1427 1417/1378/1418 1416/1377/1417 +f 1417/1378/1418 1426/1387/1427 1427/1388/1428 +f 1427/1388/1428 1418/1379/1419 1417/1378/1418 +f 1418/1379/1419 1427/1388/1428 1428/1389/1429 +f 1428/1389/1429 1419/1380/1420 1418/1379/1419 +f 1419/1380/1420 1428/1389/1429 1429/1390/1430 +f 1429/1390/1430 1420/1381/1421 1419/1380/1420 +f 1420/1381/1421 1429/1390/1430 1258/1219/1259 +f 1049/231/1050 1059/242/1060 1430/1391/1431 +f 1430/1391/1431 1421/1382/1422 1049/231/1050 +f 1421/1382/1422 1430/1391/1431 1431/1392/1432 +f 1431/1392/1432 1422/1383/1423 1421/1382/1422 +f 1422/1383/1423 1431/1392/1432 1432/1393/1433 +f 1432/1393/1433 1423/1384/1424 1422/1383/1423 +f 1423/1384/1424 1432/1393/1433 1433/1394/1434 +f 1433/1394/1434 1424/1385/1425 1423/1384/1424 +f 1424/1385/1425 1433/1394/1434 1434/1395/1435 +f 1434/1395/1435 1425/1386/1426 1424/1385/1425 +f 1425/1386/1426 1434/1395/1435 1435/1396/1436 +f 1435/1396/1436 1426/1387/1427 1425/1386/1426 +f 1426/1387/1427 1435/1396/1436 1436/1397/1437 +f 1436/1397/1437 1427/1388/1428 1426/1387/1427 +f 1427/1388/1428 1436/1397/1437 1437/1398/1438 +f 1437/1398/1438 1428/1389/1429 1427/1388/1428 +f 1428/1389/1429 1437/1398/1438 1438/1399/1439 +f 1438/1399/1439 1429/1390/1430 1428/1389/1429 +f 1429/1390/1430 1438/1399/1439 1258/1219/1259 +f 1059/242/1060 1069/253/1070 1439/1400/1440 +f 1439/1400/1440 1430/1391/1431 1059/242/1060 +f 1430/1391/1431 1439/1400/1440 1440/1401/1441 +f 1440/1401/1441 1431/1392/1432 1430/1391/1431 +f 1431/1392/1432 1440/1401/1441 1441/1402/1442 +f 1441/1402/1442 1432/1393/1433 1431/1392/1432 +f 1432/1393/1433 1441/1402/1442 1442/1403/1443 +f 1442/1403/1443 1433/1394/1434 1432/1393/1433 +f 1433/1394/1434 1442/1403/1443 1443/1404/1444 +f 1443/1404/1444 1434/1395/1435 1433/1394/1434 +f 1434/1395/1435 1443/1404/1444 1444/1405/1445 +f 1444/1405/1445 1435/1396/1436 1434/1395/1435 +f 1435/1396/1436 1444/1405/1445 1445/1406/1446 +f 1445/1406/1446 1436/1397/1437 1435/1396/1436 +f 1436/1397/1437 1445/1406/1446 1446/1407/1447 +f 1446/1407/1447 1437/1398/1438 1436/1397/1437 +f 1437/1398/1438 1446/1407/1447 1447/1408/1448 +f 1447/1408/1448 1438/1399/1439 1437/1398/1438 +f 1438/1399/1439 1447/1408/1448 1258/1219/1259 +f 1069/253/1070 1079/264/1080 1448/1409/1449 +f 1448/1409/1449 1439/1400/1440 1069/253/1070 +f 1439/1400/1440 1448/1409/1449 1449/1410/1450 +f 1449/1410/1450 1440/1401/1441 1439/1400/1440 +f 1440/1401/1441 1449/1410/1450 1450/1411/1451 +f 1450/1411/1451 1441/1402/1442 1440/1401/1441 +f 1441/1402/1442 1450/1411/1451 1451/1412/1452 +f 1451/1412/1452 1442/1403/1443 1441/1402/1442 +f 1442/1403/1443 1451/1412/1452 1452/1413/1453 +f 1452/1413/1453 1443/1404/1444 1442/1403/1443 +f 1443/1404/1444 1452/1413/1453 1453/1414/1454 +f 1453/1414/1454 1444/1405/1445 1443/1404/1444 +f 1444/1405/1445 1453/1414/1454 1454/1415/1455 +f 1454/1415/1455 1445/1406/1446 1444/1405/1445 +f 1445/1406/1446 1454/1415/1455 1455/1416/1456 +f 1455/1416/1456 1446/1407/1447 1445/1406/1446 +f 1446/1407/1447 1455/1416/1456 1456/1417/1457 +f 1456/1417/1457 1447/1408/1448 1446/1407/1447 +f 1447/1408/1448 1456/1417/1457 1258/1219/1259 +f 1079/264/1080 1089/275/1090 1457/1418/1458 +f 1457/1418/1458 1448/1409/1449 1079/264/1080 +f 1448/1409/1449 1457/1418/1458 1458/1419/1459 +f 1458/1419/1459 1449/1410/1450 1448/1409/1449 +f 1449/1410/1450 1458/1419/1459 1459/1420/1460 +f 1459/1420/1460 1450/1411/1451 1449/1410/1450 +f 1450/1411/1451 1459/1420/1460 1460/1421/1461 +f 1460/1421/1461 1451/1412/1452 1450/1411/1451 +f 1451/1412/1452 1460/1421/1461 1461/1422/1462 +f 1461/1422/1462 1452/1413/1453 1451/1412/1452 +f 1452/1413/1453 1461/1422/1462 1462/1423/1463 +f 1462/1423/1463 1453/1414/1454 1452/1413/1453 +f 1453/1414/1454 1462/1423/1463 1463/1424/1464 +f 1463/1424/1464 1454/1415/1455 1453/1414/1454 +f 1454/1415/1455 1463/1424/1464 1464/1425/1465 +f 1464/1425/1465 1455/1416/1456 1454/1415/1455 +f 1455/1416/1456 1464/1425/1465 1465/1426/1466 +f 1465/1426/1466 1456/1417/1457 1455/1416/1456 +f 1456/1417/1457 1465/1426/1466 1258/1219/1259 +f 1089/275/1090 1099/286/1100 1466/1427/1467 +f 1466/1427/1467 1457/1418/1458 1089/275/1090 +f 1457/1418/1458 1466/1427/1467 1467/1428/1468 +f 1467/1428/1468 1458/1419/1459 1457/1418/1458 +f 1458/1419/1459 1467/1428/1468 1468/1429/1469 +f 1468/1429/1469 1459/1420/1460 1458/1419/1459 +f 1459/1420/1460 1468/1429/1469 1469/1430/1470 +f 1469/1430/1470 1460/1421/1461 1459/1420/1460 +f 1460/1421/1461 1469/1430/1470 1470/1431/1471 +f 1470/1431/1471 1461/1422/1462 1460/1421/1461 +f 1461/1422/1462 1470/1431/1471 1471/1432/1472 +f 1471/1432/1472 1462/1423/1463 1461/1422/1462 +f 1462/1423/1463 1471/1432/1472 1472/1433/1473 +f 1472/1433/1473 1463/1424/1464 1462/1423/1463 +f 1463/1424/1464 1472/1433/1473 1473/1434/1474 +f 1473/1434/1474 1464/1425/1465 1463/1424/1464 +f 1464/1425/1465 1473/1434/1474 1474/1435/1475 +f 1474/1435/1475 1465/1426/1466 1464/1425/1465 +f 1465/1426/1466 1474/1435/1475 1258/1219/1259 +f 1099/286/1100 1109/297/1110 1475/1436/1476 +f 1475/1436/1476 1466/1427/1467 1099/286/1100 +f 1466/1427/1467 1475/1436/1476 1476/1437/1477 +f 1476/1437/1477 1467/1428/1468 1466/1427/1467 +f 1467/1428/1468 1476/1437/1477 1477/1438/1478 +f 1477/1438/1478 1468/1429/1469 1467/1428/1468 +f 1468/1429/1469 1477/1438/1478 1478/1439/1479 +f 1478/1439/1479 1469/1430/1470 1468/1429/1469 +f 1469/1430/1470 1478/1439/1479 1479/1440/1480 +f 1479/1440/1480 1470/1431/1471 1469/1430/1470 +f 1470/1431/1471 1479/1440/1480 1480/1441/1481 +f 1480/1441/1481 1471/1432/1472 1470/1431/1471 +f 1471/1432/1472 1480/1441/1481 1481/1442/1482 +f 1481/1442/1482 1472/1433/1473 1471/1432/1472 +f 1472/1433/1473 1481/1442/1482 1482/1443/1483 +f 1482/1443/1483 1473/1434/1474 1472/1433/1473 +f 1473/1434/1474 1482/1443/1483 1483/1444/1484 +f 1483/1444/1484 1474/1435/1475 1473/1434/1474 +f 1474/1435/1475 1483/1444/1484 1258/1219/1259 +f 1109/297/1110 1119/308/1120 1484/1445/1485 +f 1484/1445/1485 1475/1436/1476 1109/297/1110 +f 1475/1436/1476 1484/1445/1485 1485/1446/1486 +f 1485/1446/1486 1476/1437/1477 1475/1436/1476 +f 1476/1437/1477 1485/1446/1486 1486/1447/1487 +f 1486/1447/1487 1477/1438/1478 1476/1437/1477 +f 1477/1438/1478 1486/1447/1487 1487/1448/1488 +f 1487/1448/1488 1478/1439/1479 1477/1438/1478 +f 1478/1439/1479 1487/1448/1488 1488/1449/1489 +f 1488/1449/1489 1479/1440/1480 1478/1439/1479 +f 1479/1440/1480 1488/1449/1489 1489/1450/1490 +f 1489/1450/1490 1480/1441/1481 1479/1440/1480 +f 1480/1441/1481 1489/1450/1490 1490/1451/1491 +f 1490/1451/1491 1481/1442/1482 1480/1441/1481 +f 1481/1442/1482 1490/1451/1491 1491/1452/1492 +f 1491/1452/1492 1482/1443/1483 1481/1442/1482 +f 1482/1443/1483 1491/1452/1492 1492/1453/1493 +f 1492/1453/1493 1483/1444/1484 1482/1443/1483 +f 1483/1444/1484 1492/1453/1493 1258/1219/1259 +f 1119/308/1120 1129/319/1130 1493/1454/1494 +f 1493/1454/1494 1484/1445/1485 1119/308/1120 +f 1484/1445/1485 1493/1454/1494 1494/1455/1495 +f 1494/1455/1495 1485/1446/1486 1484/1445/1485 +f 1485/1446/1486 1494/1455/1495 1495/1456/1496 +f 1495/1456/1496 1486/1447/1487 1485/1446/1486 +f 1486/1447/1487 1495/1456/1496 1496/1457/1497 +f 1496/1457/1497 1487/1448/1488 1486/1447/1487 +f 1487/1448/1488 1496/1457/1497 1497/1458/1498 +f 1497/1458/1498 1488/1449/1489 1487/1448/1488 +f 1488/1449/1489 1497/1458/1498 1498/1459/1499 +f 1498/1459/1499 1489/1450/1490 1488/1449/1489 +f 1489/1450/1490 1498/1459/1499 1499/1460/1500 +f 1499/1460/1500 1490/1451/1491 1489/1450/1490 +f 1490/1451/1491 1499/1460/1500 1500/1461/1501 +f 1500/1461/1501 1491/1452/1492 1490/1451/1491 +f 1491/1452/1492 1500/1461/1501 1501/1462/1502 +f 1501/1462/1502 1492/1453/1493 1491/1452/1492 +f 1492/1453/1493 1501/1462/1502 1258/1219/1259 +f 1129/319/1130 1139/330/1140 1502/1463/1503 +f 1502/1463/1503 1493/1454/1494 1129/319/1130 +f 1493/1454/1494 1502/1463/1503 1503/1464/1504 +f 1503/1464/1504 1494/1455/1495 1493/1454/1494 +f 1494/1455/1495 1503/1464/1504 1504/1465/1505 +f 1504/1465/1505 1495/1456/1496 1494/1455/1495 +f 1495/1456/1496 1504/1465/1505 1505/1466/1506 +f 1505/1466/1506 1496/1457/1497 1495/1456/1496 +f 1496/1457/1497 1505/1466/1506 1506/1467/1507 +f 1506/1467/1507 1497/1458/1498 1496/1457/1497 +f 1497/1458/1498 1506/1467/1507 1507/1468/1508 +f 1507/1468/1508 1498/1459/1499 1497/1458/1498 +f 1498/1459/1499 1507/1468/1508 1508/1469/1509 +f 1508/1469/1509 1499/1460/1500 1498/1459/1499 +f 1499/1460/1500 1508/1469/1509 1509/1470/1510 +f 1509/1470/1510 1500/1461/1501 1499/1460/1500 +f 1500/1461/1501 1509/1470/1510 1510/1471/1511 +f 1510/1471/1511 1501/1462/1502 1500/1461/1501 +f 1501/1462/1502 1510/1471/1511 1258/1219/1259 +f 1139/330/1140 1149/341/1150 1511/1472/1512 +f 1511/1472/1512 1502/1463/1503 1139/330/1140 +f 1502/1463/1503 1511/1472/1512 1512/1473/1513 +f 1512/1473/1513 1503/1464/1504 1502/1463/1503 +f 1503/1464/1504 1512/1473/1513 1513/1474/1514 +f 1513/1474/1514 1504/1465/1505 1503/1464/1504 +f 1504/1465/1505 1513/1474/1514 1514/1475/1515 +f 1514/1475/1515 1505/1466/1506 1504/1465/1505 +f 1505/1466/1506 1514/1475/1515 1515/1476/1516 +f 1515/1476/1516 1506/1467/1507 1505/1466/1506 +f 1506/1467/1507 1515/1476/1516 1516/1477/1517 +f 1516/1477/1517 1507/1468/1508 1506/1467/1507 +f 1507/1468/1508 1516/1477/1517 1517/1478/1518 +f 1517/1478/1518 1508/1469/1509 1507/1468/1508 +f 1508/1469/1509 1517/1478/1518 1518/1479/1519 +f 1518/1479/1519 1509/1470/1510 1508/1469/1509 +f 1509/1470/1510 1518/1479/1519 1519/1480/1520 +f 1519/1480/1520 1510/1471/1511 1509/1470/1510 +f 1510/1471/1511 1519/1480/1520 1258/1219/1259 +f 1149/341/1150 1159/352/1160 1520/1481/1521 +f 1520/1481/1521 1511/1472/1512 1149/341/1150 +f 1511/1472/1512 1520/1481/1521 1521/1482/1522 +f 1521/1482/1522 1512/1473/1513 1511/1472/1512 +f 1512/1473/1513 1521/1482/1522 1522/1483/1523 +f 1522/1483/1523 1513/1474/1514 1512/1473/1513 +f 1513/1474/1514 1522/1483/1523 1523/1484/1524 +f 1523/1484/1524 1514/1475/1515 1513/1474/1514 +f 1514/1475/1515 1523/1484/1524 1524/1485/1525 +f 1524/1485/1525 1515/1476/1516 1514/1475/1515 +f 1515/1476/1516 1524/1485/1525 1525/1486/1526 +f 1525/1486/1526 1516/1477/1517 1515/1476/1516 +f 1516/1477/1517 1525/1486/1526 1526/1487/1527 +f 1526/1487/1527 1517/1478/1518 1516/1477/1517 +f 1517/1478/1518 1526/1487/1527 1527/1488/1528 +f 1527/1488/1528 1518/1479/1519 1517/1478/1518 +f 1518/1479/1519 1527/1488/1528 1528/1489/1529 +f 1528/1489/1529 1519/1480/1520 1518/1479/1519 +f 1519/1480/1520 1528/1489/1529 1258/1219/1259 +f 1159/352/1160 1169/363/1170 1529/1490/1530 +f 1529/1490/1530 1520/1481/1521 1159/352/1160 +f 1520/1481/1521 1529/1490/1530 1530/1491/1531 +f 1530/1491/1531 1521/1482/1522 1520/1481/1521 +f 1521/1482/1522 1530/1491/1531 1531/1492/1532 +f 1531/1492/1532 1522/1483/1523 1521/1482/1522 +f 1522/1483/1523 1531/1492/1532 1532/1493/1533 +f 1532/1493/1533 1523/1484/1524 1522/1483/1523 +f 1523/1484/1524 1532/1493/1533 1533/1494/1534 +f 1533/1494/1534 1524/1485/1525 1523/1484/1524 +f 1524/1485/1525 1533/1494/1534 1534/1495/1535 +f 1534/1495/1535 1525/1486/1526 1524/1485/1525 +f 1525/1486/1526 1534/1495/1535 1535/1496/1536 +f 1535/1496/1536 1526/1487/1527 1525/1486/1526 +f 1526/1487/1527 1535/1496/1536 1536/1497/1537 +f 1536/1497/1537 1527/1488/1528 1526/1487/1527 +f 1527/1488/1528 1536/1497/1537 1537/1498/1538 +f 1537/1498/1538 1528/1489/1529 1527/1488/1528 +f 1528/1489/1529 1537/1498/1538 1258/1219/1259 +f 1169/363/1170 1179/374/1180 1538/1499/1539 +f 1538/1499/1539 1529/1490/1530 1169/363/1170 +f 1529/1490/1530 1538/1499/1539 1539/1500/1540 +f 1539/1500/1540 1530/1491/1531 1529/1490/1530 +f 1530/1491/1531 1539/1500/1540 1540/1501/1541 +f 1540/1501/1541 1531/1492/1532 1530/1491/1531 +f 1531/1492/1532 1540/1501/1541 1541/1502/1542 +f 1541/1502/1542 1532/1493/1533 1531/1492/1532 +f 1532/1493/1533 1541/1502/1542 1542/1503/1543 +f 1542/1503/1543 1533/1494/1534 1532/1493/1533 +f 1533/1494/1534 1542/1503/1543 1543/1504/1544 +f 1543/1504/1544 1534/1495/1535 1533/1494/1534 +f 1534/1495/1535 1543/1504/1544 1544/1505/1545 +f 1544/1505/1545 1535/1496/1536 1534/1495/1535 +f 1535/1496/1536 1544/1505/1545 1545/1506/1546 +f 1545/1506/1546 1536/1497/1537 1535/1496/1536 +f 1536/1497/1537 1545/1506/1546 1546/1507/1547 +f 1546/1507/1547 1537/1498/1538 1536/1497/1537 +f 1537/1498/1538 1546/1507/1547 1258/1219/1259 +f 1179/374/1180 1189/385/1190 1547/1508/1548 +f 1547/1508/1548 1538/1499/1539 1179/374/1180 +f 1538/1499/1539 1547/1508/1548 1548/1509/1549 +f 1548/1509/1549 1539/1500/1540 1538/1499/1539 +f 1539/1500/1540 1548/1509/1549 1549/1510/1550 +f 1549/1510/1550 1540/1501/1541 1539/1500/1540 +f 1540/1501/1541 1549/1510/1550 1550/1511/1551 +f 1550/1511/1551 1541/1502/1542 1540/1501/1541 +f 1541/1502/1542 1550/1511/1551 1551/1512/1552 +f 1551/1512/1552 1542/1503/1543 1541/1502/1542 +f 1542/1503/1543 1551/1512/1552 1552/1513/1553 +f 1552/1513/1553 1543/1504/1544 1542/1503/1543 +f 1543/1504/1544 1552/1513/1553 1553/1514/1554 +f 1553/1514/1554 1544/1505/1545 1543/1504/1544 +f 1544/1505/1545 1553/1514/1554 1554/1515/1555 +f 1554/1515/1555 1545/1506/1546 1544/1505/1545 +f 1545/1506/1546 1554/1515/1555 1555/1516/1556 +f 1555/1516/1556 1546/1507/1547 1545/1506/1546 +f 1546/1507/1547 1555/1516/1556 1258/1219/1259 +f 1189/385/1190 1199/396/1200 1556/1517/1557 +f 1556/1517/1557 1547/1508/1548 1189/385/1190 +f 1547/1508/1548 1556/1517/1557 1557/1518/1558 +f 1557/1518/1558 1548/1509/1549 1547/1508/1548 +f 1548/1509/1549 1557/1518/1558 1558/1519/1559 +f 1558/1519/1559 1549/1510/1550 1548/1509/1549 +f 1549/1510/1550 1558/1519/1559 1559/1520/1560 +f 1559/1520/1560 1550/1511/1551 1549/1510/1550 +f 1550/1511/1551 1559/1520/1560 1560/1521/1561 +f 1560/1521/1561 1551/1512/1552 1550/1511/1551 +f 1551/1512/1552 1560/1521/1561 1561/1522/1562 +f 1561/1522/1562 1552/1513/1553 1551/1512/1552 +f 1552/1513/1553 1561/1522/1562 1562/1523/1563 +f 1562/1523/1563 1553/1514/1554 1552/1513/1553 +f 1553/1514/1554 1562/1523/1563 1563/1524/1564 +f 1563/1524/1564 1554/1515/1555 1553/1514/1554 +f 1554/1515/1555 1563/1524/1564 1564/1525/1565 +f 1564/1525/1565 1555/1516/1556 1554/1515/1555 +f 1555/1516/1556 1564/1525/1565 1258/1219/1259 +f 1199/396/1200 1209/407/1210 1565/1526/1566 +f 1565/1526/1566 1556/1517/1557 1199/396/1200 +f 1556/1517/1557 1565/1526/1566 1566/1527/1567 +f 1566/1527/1567 1557/1518/1558 1556/1517/1557 +f 1557/1518/1558 1566/1527/1567 1567/1528/1568 +f 1567/1528/1568 1558/1519/1559 1557/1518/1558 +f 1558/1519/1559 1567/1528/1568 1568/1529/1569 +f 1568/1529/1569 1559/1520/1560 1558/1519/1559 +f 1559/1520/1560 1568/1529/1569 1569/1530/1570 +f 1569/1530/1570 1560/1521/1561 1559/1520/1560 +f 1560/1521/1561 1569/1530/1570 1570/1531/1571 +f 1570/1531/1571 1561/1522/1562 1560/1521/1561 +f 1561/1522/1562 1570/1531/1571 1571/1532/1572 +f 1571/1532/1572 1562/1523/1563 1561/1522/1562 +f 1562/1523/1563 1571/1532/1572 1572/1533/1573 +f 1572/1533/1573 1563/1524/1564 1562/1523/1563 +f 1563/1524/1564 1572/1533/1573 1573/1534/1574 +f 1573/1534/1574 1564/1525/1565 1563/1524/1564 +f 1564/1525/1565 1573/1534/1574 1258/1219/1259 +f 1209/407/1210 1219/418/1220 1574/1535/1575 +f 1574/1535/1575 1565/1526/1566 1209/407/1210 +f 1565/1526/1566 1574/1535/1575 1575/1536/1576 +f 1575/1536/1576 1566/1527/1567 1565/1526/1566 +f 1566/1527/1567 1575/1536/1576 1576/1537/1577 +f 1576/1537/1577 1567/1528/1568 1566/1527/1567 +f 1567/1528/1568 1576/1537/1577 1577/1538/1578 +f 1577/1538/1578 1568/1529/1569 1567/1528/1568 +f 1568/1529/1569 1577/1538/1578 1578/1539/1579 +f 1578/1539/1579 1569/1530/1570 1568/1529/1569 +f 1569/1530/1570 1578/1539/1579 1579/1540/1580 +f 1579/1540/1580 1570/1531/1571 1569/1530/1570 +f 1570/1531/1571 1579/1540/1580 1580/1541/1581 +f 1580/1541/1581 1571/1532/1572 1570/1531/1571 +f 1571/1532/1572 1580/1541/1581 1581/1542/1582 +f 1581/1542/1582 1572/1533/1573 1571/1532/1572 +f 1572/1533/1573 1581/1542/1582 1582/1543/1583 +f 1582/1543/1583 1573/1534/1574 1572/1533/1573 +f 1573/1534/1574 1582/1543/1583 1258/1219/1259 +f 1219/418/1220 1229/429/1230 1583/1544/1584 +f 1583/1544/1584 1574/1535/1575 1219/418/1220 +f 1574/1535/1575 1583/1544/1584 1584/1545/1585 +f 1584/1545/1585 1575/1536/1576 1574/1535/1575 +f 1575/1536/1576 1584/1545/1585 1585/1546/1586 +f 1585/1546/1586 1576/1537/1577 1575/1536/1576 +f 1576/1537/1577 1585/1546/1586 1586/1547/1587 +f 1586/1547/1587 1577/1538/1578 1576/1537/1577 +f 1577/1538/1578 1586/1547/1587 1587/1548/1588 +f 1587/1548/1588 1578/1539/1579 1577/1538/1578 +f 1578/1539/1579 1587/1548/1588 1588/1549/1589 +f 1588/1549/1589 1579/1540/1580 1578/1539/1579 +f 1579/1540/1580 1588/1549/1589 1589/1550/1590 +f 1589/1550/1590 1580/1541/1581 1579/1540/1580 +f 1580/1541/1581 1589/1550/1590 1590/1551/1591 +f 1590/1551/1591 1581/1542/1582 1580/1541/1581 +f 1581/1542/1582 1590/1551/1591 1591/1552/1592 +f 1591/1552/1592 1582/1543/1583 1581/1542/1582 +f 1582/1543/1583 1591/1552/1592 1258/1219/1259 +f 1229/429/1230 1239/440/1240 1592/1553/1593 +f 1592/1553/1593 1583/1544/1584 1229/429/1230 +f 1583/1544/1584 1592/1553/1593 1593/1554/1594 +f 1593/1554/1594 1584/1545/1585 1583/1544/1584 +f 1584/1545/1585 1593/1554/1594 1594/1555/1595 +f 1594/1555/1595 1585/1546/1586 1584/1545/1585 +f 1585/1546/1586 1594/1555/1595 1595/1556/1596 +f 1595/1556/1596 1586/1547/1587 1585/1546/1586 +f 1586/1547/1587 1595/1556/1596 1596/1557/1597 +f 1596/1557/1597 1587/1548/1588 1586/1547/1587 +f 1587/1548/1588 1596/1557/1597 1597/1558/1598 +f 1597/1558/1598 1588/1549/1589 1587/1548/1588 +f 1588/1549/1589 1597/1558/1598 1598/1559/1599 +f 1598/1559/1599 1589/1550/1590 1588/1549/1589 +f 1589/1550/1590 1598/1559/1599 1599/1560/1600 +f 1599/1560/1600 1590/1551/1591 1589/1550/1590 +f 1590/1551/1591 1599/1560/1600 1600/1561/1601 +f 1600/1561/1601 1591/1552/1592 1590/1551/1591 +f 1591/1552/1592 1600/1561/1601 1258/1219/1259 +f 1239/440/1240 859/22/860 1241/1202/1242 +f 1241/1202/1242 1592/1553/1593 1239/440/1240 +f 1592/1553/1593 1241/1202/1242 1243/1204/1244 +f 1243/1204/1244 1593/1554/1594 1592/1553/1593 +f 1593/1554/1594 1243/1204/1244 1245/1206/1246 +f 1245/1206/1246 1594/1555/1595 1593/1554/1594 +f 1594/1555/1595 1245/1206/1246 1247/1208/1248 +f 1247/1208/1248 1595/1556/1596 1594/1555/1595 +f 1595/1556/1596 1247/1208/1248 1249/1210/1250 +f 1249/1210/1250 1596/1557/1597 1595/1556/1596 +f 1596/1557/1597 1249/1210/1250 1251/1212/1252 +f 1251/1212/1252 1597/1558/1598 1596/1557/1597 +f 1597/1558/1598 1251/1212/1252 1253/1214/1254 +f 1253/1214/1254 1598/1559/1599 1597/1558/1598 +f 1598/1559/1599 1253/1214/1254 1255/1216/1256 +f 1255/1216/1256 1599/1560/1600 1598/1559/1599 +f 1599/1560/1600 1255/1216/1256 1257/1218/1258 +f 1257/1218/1258 1600/1561/1601 1599/1560/1600 +f 1600/1561/1601 1257/1218/1258 1258/1219/1259 +f 1601/1562/1602 1602/1563/1603 1603/1564/1604 +f 1603/1564/1604 1604/1565/1605 1601/1562/1602 +f 1604/1565/1605 1603/1564/1604 1605/1566/1606 +f 1605/1566/1606 1606/1567/1607 1604/1565/1605 +f 1606/1567/1607 1605/1566/1606 1607/1568/1608 +f 1607/1568/1608 1608/1569/1609 1606/1567/1607 +f 1608/1569/1609 1607/1568/1608 1609/1570/1610 +f 1609/1570/1610 1610/1571/1611 1608/1569/1609 +f 1610/1571/1611 1609/1570/1610 1611/1572/1612 +f 1611/1572/1612 1612/1573/1613 1610/1571/1611 +f 1612/1573/1613 1611/1572/1612 1613/1574/1614 +f 1613/1574/1614 1614/1575/1615 1612/1573/1613 +f 1614/1575/1615 1613/1574/1614 1615/1576/1616 +f 1615/1576/1616 1616/1577/1617 1614/1575/1615 +f 1616/1577/1617 1615/1576/1616 1617/1578/1618 +f 1617/1578/1618 1618/1579/1619 1616/1577/1617 +f 1618/1579/1619 1617/1578/1618 1619/1580/1620 +f 1619/1580/1620 1620/1581/1621 1618/1579/1619 +f 1620/1581/1621 1619/1580/1620 1621/1582/1622 +f 1621/1582/1622 1622/1583/1623 1620/1581/1621 +f 1602/1563/1603 1623/1584/1624 1624/1585/1625 +f 1624/1585/1625 1603/1564/1604 1602/1563/1603 +f 1603/1564/1604 1624/1585/1625 1625/1586/1626 +f 1625/1586/1626 1605/1566/1606 1603/1564/1604 +f 1605/1566/1606 1625/1586/1626 1626/1587/1627 +f 1626/1587/1627 1607/1568/1608 1605/1566/1606 +f 1607/1568/1608 1626/1587/1627 1627/1588/1628 +f 1627/1588/1628 1609/1570/1610 1607/1568/1608 +f 1609/1570/1610 1627/1588/1628 1628/1589/1629 +f 1628/1589/1629 1611/1572/1612 1609/1570/1610 +f 1611/1572/1612 1628/1589/1629 1629/1590/1630 +f 1629/1590/1630 1613/1574/1614 1611/1572/1612 +f 1613/1574/1614 1629/1590/1630 1630/1591/1631 +f 1630/1591/1631 1615/1576/1616 1613/1574/1614 +f 1615/1576/1616 1630/1591/1631 1631/1592/1632 +f 1631/1592/1632 1617/1578/1618 1615/1576/1616 +f 1617/1578/1618 1631/1592/1632 1632/1593/1633 +f 1632/1593/1633 1619/1580/1620 1617/1578/1618 +f 1619/1580/1620 1632/1593/1633 1633/1594/1634 +f 1633/1594/1634 1621/1582/1622 1619/1580/1620 +f 1623/1584/1624 1634/1595/1635 1635/1596/1636 +f 1635/1596/1636 1624/1585/1625 1623/1584/1624 +f 1624/1585/1625 1635/1596/1636 1636/1597/1637 +f 1636/1597/1637 1625/1586/1626 1624/1585/1625 +f 1625/1586/1626 1636/1597/1637 1637/1598/1638 +f 1637/1598/1638 1626/1587/1627 1625/1586/1626 +f 1626/1587/1627 1637/1598/1638 1638/1599/1639 +f 1638/1599/1639 1627/1588/1628 1626/1587/1627 +f 1627/1588/1628 1638/1599/1639 1639/1600/1640 +f 1639/1600/1640 1628/1589/1629 1627/1588/1628 +f 1628/1589/1629 1639/1600/1640 1640/1601/1641 +f 1640/1601/1641 1629/1590/1630 1628/1589/1629 +f 1629/1590/1630 1640/1601/1641 1641/1602/1642 +f 1641/1602/1642 1630/1591/1631 1629/1590/1630 +f 1630/1591/1631 1641/1602/1642 1642/1603/1643 +f 1642/1603/1643 1631/1592/1632 1630/1591/1631 +f 1631/1592/1632 1642/1603/1643 1643/1604/1644 +f 1643/1604/1644 1632/1593/1633 1631/1592/1632 +f 1632/1593/1633 1643/1604/1644 1644/1605/1645 +f 1644/1605/1645 1633/1594/1634 1632/1593/1633 +f 1634/1595/1635 1645/1606/1646 1646/1607/1647 +f 1646/1607/1647 1635/1596/1636 1634/1595/1635 +f 1635/1596/1636 1646/1607/1647 1647/1608/1648 +f 1647/1608/1648 1636/1597/1637 1635/1596/1636 +f 1636/1597/1637 1647/1608/1648 1648/1609/1649 +f 1648/1609/1649 1637/1598/1638 1636/1597/1637 +f 1637/1598/1638 1648/1609/1649 1649/1610/1650 +f 1649/1610/1650 1638/1599/1639 1637/1598/1638 +f 1638/1599/1639 1649/1610/1650 1650/1611/1651 +f 1650/1611/1651 1639/1600/1640 1638/1599/1639 +f 1639/1600/1640 1650/1611/1651 1651/1612/1652 +f 1651/1612/1652 1640/1601/1641 1639/1600/1640 +f 1640/1601/1641 1651/1612/1652 1652/1613/1653 +f 1652/1613/1653 1641/1602/1642 1640/1601/1641 +f 1641/1602/1642 1652/1613/1653 1653/1614/1654 +f 1653/1614/1654 1642/1603/1643 1641/1602/1642 +f 1642/1603/1643 1653/1614/1654 1654/1615/1655 +f 1654/1615/1655 1643/1604/1644 1642/1603/1643 +f 1643/1604/1644 1654/1615/1655 1655/1616/1656 +f 1655/1616/1656 1644/1605/1645 1643/1604/1644 +f 1645/1606/1646 1656/1617/1657 1657/1618/1658 +f 1657/1618/1658 1646/1607/1647 1645/1606/1646 +f 1646/1607/1647 1657/1618/1658 1658/1619/1659 +f 1658/1619/1659 1647/1608/1648 1646/1607/1647 +f 1647/1608/1648 1658/1619/1659 1659/1620/1660 +f 1659/1620/1660 1648/1609/1649 1647/1608/1648 +f 1648/1609/1649 1659/1620/1660 1660/1621/1661 +f 1660/1621/1661 1649/1610/1650 1648/1609/1649 +f 1649/1610/1650 1660/1621/1661 1661/1622/1662 +f 1661/1622/1662 1650/1611/1651 1649/1610/1650 +f 1650/1611/1651 1661/1622/1662 1662/1623/1663 +f 1662/1623/1663 1651/1612/1652 1650/1611/1651 +f 1651/1612/1652 1662/1623/1663 1663/1624/1664 +f 1663/1624/1664 1652/1613/1653 1651/1612/1652 +f 1652/1613/1653 1663/1624/1664 1664/1625/1665 +f 1664/1625/1665 1653/1614/1654 1652/1613/1653 +f 1653/1614/1654 1664/1625/1665 1665/1626/1666 +f 1665/1626/1666 1654/1615/1655 1653/1614/1654 +f 1654/1615/1655 1665/1626/1666 1666/1627/1667 +f 1666/1627/1667 1655/1616/1656 1654/1615/1655 +f 1656/1617/1657 1667/1628/1668 1668/1629/1669 +f 1668/1629/1669 1657/1618/1658 1656/1617/1657 +f 1657/1618/1658 1668/1629/1669 1669/1630/1670 +f 1669/1630/1670 1658/1619/1659 1657/1618/1658 +f 1658/1619/1659 1669/1630/1670 1670/1631/1671 +f 1670/1631/1671 1659/1620/1660 1658/1619/1659 +f 1659/1620/1660 1670/1631/1671 1671/1632/1672 +f 1671/1632/1672 1660/1621/1661 1659/1620/1660 +f 1660/1621/1661 1671/1632/1672 1672/1633/1673 +f 1672/1633/1673 1661/1622/1662 1660/1621/1661 +f 1661/1622/1662 1672/1633/1673 1673/1634/1674 +f 1673/1634/1674 1662/1623/1663 1661/1622/1662 +f 1662/1623/1663 1673/1634/1674 1674/1635/1675 +f 1674/1635/1675 1663/1624/1664 1662/1623/1663 +f 1663/1624/1664 1674/1635/1675 1675/1636/1676 +f 1675/1636/1676 1664/1625/1665 1663/1624/1664 +f 1664/1625/1665 1675/1636/1676 1676/1637/1677 +f 1676/1637/1677 1665/1626/1666 1664/1625/1665 +f 1665/1626/1666 1676/1637/1677 1677/1638/1678 +f 1677/1638/1678 1666/1627/1667 1665/1626/1666 +f 1667/1628/1668 1678/1639/1679 1679/1640/1680 +f 1679/1640/1680 1668/1629/1669 1667/1628/1668 +f 1668/1629/1669 1679/1640/1680 1680/1641/1681 +f 1680/1641/1681 1669/1630/1670 1668/1629/1669 +f 1669/1630/1670 1680/1641/1681 1681/1642/1682 +f 1681/1642/1682 1670/1631/1671 1669/1630/1670 +f 1670/1631/1671 1681/1642/1682 1682/1643/1683 +f 1682/1643/1683 1671/1632/1672 1670/1631/1671 +f 1671/1632/1672 1682/1643/1683 1683/1644/1684 +f 1683/1644/1684 1672/1633/1673 1671/1632/1672 +f 1672/1633/1673 1683/1644/1684 1684/1645/1685 +f 1684/1645/1685 1673/1634/1674 1672/1633/1673 +f 1673/1634/1674 1684/1645/1685 1685/1646/1686 +f 1685/1646/1686 1674/1635/1675 1673/1634/1674 +f 1674/1635/1675 1685/1646/1686 1686/1647/1687 +f 1686/1647/1687 1675/1636/1676 1674/1635/1675 +f 1675/1636/1676 1686/1647/1687 1687/1648/1688 +f 1687/1648/1688 1676/1637/1677 1675/1636/1676 +f 1676/1637/1677 1687/1648/1688 1688/1649/1689 +f 1688/1649/1689 1677/1638/1678 1676/1637/1677 +f 1678/1639/1679 1689/1650/1690 1690/1651/1691 +f 1690/1651/1691 1679/1640/1680 1678/1639/1679 +f 1679/1640/1680 1690/1651/1691 1691/1652/1692 +f 1691/1652/1692 1680/1641/1681 1679/1640/1680 +f 1680/1641/1681 1691/1652/1692 1692/1653/1693 +f 1692/1653/1693 1681/1642/1682 1680/1641/1681 +f 1681/1642/1682 1692/1653/1693 1693/1654/1694 +f 1693/1654/1694 1682/1643/1683 1681/1642/1682 +f 1682/1643/1683 1693/1654/1694 1694/1655/1695 +f 1694/1655/1695 1683/1644/1684 1682/1643/1683 +f 1683/1644/1684 1694/1655/1695 1695/1656/1696 +f 1695/1656/1696 1684/1645/1685 1683/1644/1684 +f 1684/1645/1685 1695/1656/1696 1696/1657/1697 +f 1696/1657/1697 1685/1646/1686 1684/1645/1685 +f 1685/1646/1686 1696/1657/1697 1697/1658/1698 +f 1697/1658/1698 1686/1647/1687 1685/1646/1686 +f 1686/1647/1687 1697/1658/1698 1698/1659/1699 +f 1698/1659/1699 1687/1648/1688 1686/1647/1687 +f 1687/1648/1688 1698/1659/1699 1699/1660/1700 +f 1699/1660/1700 1688/1649/1689 1687/1648/1688 +f 1689/1650/1690 1700/1661/1701 1701/1662/1702 +f 1701/1662/1702 1690/1651/1691 1689/1650/1690 +f 1690/1651/1691 1701/1662/1702 1702/1663/1703 +f 1702/1663/1703 1691/1652/1692 1690/1651/1691 +f 1691/1652/1692 1702/1663/1703 1703/1664/1704 +f 1703/1664/1704 1692/1653/1693 1691/1652/1692 +f 1692/1653/1693 1703/1664/1704 1704/1665/1705 +f 1704/1665/1705 1693/1654/1694 1692/1653/1693 +f 1693/1654/1694 1704/1665/1705 1705/1666/1706 +f 1705/1666/1706 1694/1655/1695 1693/1654/1694 +f 1694/1655/1695 1705/1666/1706 1706/1667/1707 +f 1706/1667/1707 1695/1656/1696 1694/1655/1695 +f 1695/1656/1696 1706/1667/1707 1707/1668/1708 +f 1707/1668/1708 1696/1657/1697 1695/1656/1696 +f 1696/1657/1697 1707/1668/1708 1708/1669/1709 +f 1708/1669/1709 1697/1658/1698 1696/1657/1697 +f 1697/1658/1698 1708/1669/1709 1709/1670/1710 +f 1709/1670/1710 1698/1659/1699 1697/1658/1698 +f 1698/1659/1699 1709/1670/1710 1710/1671/1711 +f 1710/1671/1711 1699/1660/1700 1698/1659/1699 +f 1700/1661/1701 1711/231/1712 1712/1672/1713 +f 1712/1672/1713 1701/1662/1702 1700/1661/1701 +f 1701/1662/1702 1712/1672/1713 1713/1673/1714 +f 1713/1673/1714 1702/1663/1703 1701/1662/1702 +f 1702/1663/1703 1713/1673/1714 1714/1674/1715 +f 1714/1674/1715 1703/1664/1704 1702/1663/1703 +f 1703/1664/1704 1714/1674/1715 1715/1675/1716 +f 1715/1675/1716 1704/1665/1705 1703/1664/1704 +f 1704/1665/1705 1715/1675/1716 1716/1676/1717 +f 1716/1676/1717 1705/1666/1706 1704/1665/1705 +f 1705/1666/1706 1716/1676/1717 1717/1677/1718 +f 1717/1677/1718 1706/1667/1707 1705/1666/1706 +f 1706/1667/1707 1717/1677/1718 1718/1678/1719 +f 1718/1678/1719 1707/1668/1708 1706/1667/1707 +f 1707/1668/1708 1718/1678/1719 1719/1679/1720 +f 1719/1679/1720 1708/1669/1709 1707/1668/1708 +f 1708/1669/1709 1719/1679/1720 1720/1680/1721 +f 1720/1680/1721 1709/1670/1710 1708/1669/1709 +f 1709/1670/1710 1720/1680/1721 1721/1681/1722 +f 1721/1681/1722 1710/1671/1711 1709/1670/1710 +f 1711/231/1712 1722/1682/1723 1723/1683/1724 +f 1723/1683/1724 1712/1672/1713 1711/231/1712 +f 1712/1672/1713 1723/1683/1724 1724/1684/1725 +f 1724/1684/1725 1713/1673/1714 1712/1672/1713 +f 1713/1673/1714 1724/1684/1725 1725/1685/1726 +f 1725/1685/1726 1714/1674/1715 1713/1673/1714 +f 1714/1674/1715 1725/1685/1726 1726/1686/1727 +f 1726/1686/1727 1715/1675/1716 1714/1674/1715 +f 1715/1675/1716 1726/1686/1727 1727/1687/1728 +f 1727/1687/1728 1716/1676/1717 1715/1675/1716 +f 1716/1676/1717 1727/1687/1728 1728/1688/1729 +f 1728/1688/1729 1717/1677/1718 1716/1676/1717 +f 1717/1677/1718 1728/1688/1729 1729/1689/1730 +f 1729/1689/1730 1718/1678/1719 1717/1677/1718 +f 1718/1678/1719 1729/1689/1730 1730/1690/1731 +f 1730/1690/1731 1719/1679/1720 1718/1678/1719 +f 1719/1679/1720 1730/1690/1731 1731/1691/1732 +f 1731/1691/1732 1720/1680/1721 1719/1679/1720 +f 1720/1680/1721 1731/1691/1732 1732/1692/1733 +f 1732/1692/1733 1721/1681/1722 1720/1680/1721 +f 1722/1682/1723 1733/1693/1734 1734/1694/1735 +f 1734/1694/1735 1723/1683/1724 1722/1682/1723 +f 1723/1683/1724 1734/1694/1735 1735/1695/1736 +f 1735/1695/1736 1724/1684/1725 1723/1683/1724 +f 1724/1684/1725 1735/1695/1736 1736/1696/1737 +f 1736/1696/1737 1725/1685/1726 1724/1684/1725 +f 1725/1685/1726 1736/1696/1737 1737/1697/1738 +f 1737/1697/1738 1726/1686/1727 1725/1685/1726 +f 1726/1686/1727 1737/1697/1738 1738/1698/1739 +f 1738/1698/1739 1727/1687/1728 1726/1686/1727 +f 1727/1687/1728 1738/1698/1739 1739/1699/1740 +f 1739/1699/1740 1728/1688/1729 1727/1687/1728 +f 1728/1688/1729 1739/1699/1740 1740/1700/1741 +f 1740/1700/1741 1729/1689/1730 1728/1688/1729 +f 1729/1689/1730 1740/1700/1741 1741/1701/1742 +f 1741/1701/1742 1730/1690/1731 1729/1689/1730 +f 1730/1690/1731 1741/1701/1742 1742/1702/1743 +f 1742/1702/1743 1731/1691/1732 1730/1690/1731 +f 1731/1691/1732 1742/1702/1743 1743/1703/1744 +f 1743/1703/1744 1732/1692/1733 1731/1691/1732 +f 1733/1693/1734 1744/1704/1745 1745/1705/1746 +f 1745/1705/1746 1734/1694/1735 1733/1693/1734 +f 1734/1694/1735 1745/1705/1746 1746/1706/1747 +f 1746/1706/1747 1735/1695/1736 1734/1694/1735 +f 1735/1695/1736 1746/1706/1747 1747/1707/1748 +f 1747/1707/1748 1736/1696/1737 1735/1695/1736 +f 1736/1696/1737 1747/1707/1748 1748/1708/1749 +f 1748/1708/1749 1737/1697/1738 1736/1696/1737 +f 1737/1697/1738 1748/1708/1749 1749/1709/1750 +f 1749/1709/1750 1738/1698/1739 1737/1697/1738 +f 1738/1698/1739 1749/1709/1750 1750/1710/1751 +f 1750/1710/1751 1739/1699/1740 1738/1698/1739 +f 1739/1699/1740 1750/1710/1751 1751/1711/1752 +f 1751/1711/1752 1740/1700/1741 1739/1699/1740 +f 1740/1700/1741 1751/1711/1752 1752/1712/1753 +f 1752/1712/1753 1741/1701/1742 1740/1700/1741 +f 1741/1701/1742 1752/1712/1753 1753/1713/1754 +f 1753/1713/1754 1742/1702/1743 1741/1701/1742 +f 1742/1702/1743 1753/1713/1754 1754/1714/1755 +f 1754/1714/1755 1743/1703/1744 1742/1702/1743 +f 1744/1704/1745 1755/1715/1756 1756/1716/1757 +f 1756/1716/1757 1745/1705/1746 1744/1704/1745 +f 1745/1705/1746 1756/1716/1757 1757/1717/1758 +f 1757/1717/1758 1746/1706/1747 1745/1705/1746 +f 1746/1706/1747 1757/1717/1758 1758/1718/1759 +f 1758/1718/1759 1747/1707/1748 1746/1706/1747 +f 1747/1707/1748 1758/1718/1759 1759/1719/1760 +f 1759/1719/1760 1748/1708/1749 1747/1707/1748 +f 1748/1708/1749 1759/1719/1760 1760/1720/1761 +f 1760/1720/1761 1749/1709/1750 1748/1708/1749 +f 1749/1709/1750 1760/1720/1761 1761/1721/1762 +f 1761/1721/1762 1750/1710/1751 1749/1709/1750 +f 1750/1710/1751 1761/1721/1762 1762/1722/1763 +f 1762/1722/1763 1751/1711/1752 1750/1710/1751 +f 1751/1711/1752 1762/1722/1763 1763/1723/1764 +f 1763/1723/1764 1752/1712/1753 1751/1711/1752 +f 1752/1712/1753 1763/1723/1764 1764/1724/1765 +f 1764/1724/1765 1753/1713/1754 1752/1712/1753 +f 1753/1713/1754 1764/1724/1765 1765/1725/1766 +f 1765/1725/1766 1754/1714/1755 1753/1713/1754 +f 1755/1715/1756 1766/1726/1767 1767/1727/1768 +f 1767/1727/1768 1756/1716/1757 1755/1715/1756 +f 1756/1716/1757 1767/1727/1768 1768/1728/1769 +f 1768/1728/1769 1757/1717/1758 1756/1716/1757 +f 1757/1717/1758 1768/1728/1769 1769/1729/1770 +f 1769/1729/1770 1758/1718/1759 1757/1717/1758 +f 1758/1718/1759 1769/1729/1770 1770/1730/1771 +f 1770/1730/1771 1759/1719/1760 1758/1718/1759 +f 1759/1719/1760 1770/1730/1771 1771/1731/1772 +f 1771/1731/1772 1760/1720/1761 1759/1719/1760 +f 1760/1720/1761 1771/1731/1772 1772/1732/1773 +f 1772/1732/1773 1761/1721/1762 1760/1720/1761 +f 1761/1721/1762 1772/1732/1773 1773/1733/1774 +f 1773/1733/1774 1762/1722/1763 1761/1721/1762 +f 1762/1722/1763 1773/1733/1774 1774/1734/1775 +f 1774/1734/1775 1763/1723/1764 1762/1722/1763 +f 1763/1723/1764 1774/1734/1775 1775/1735/1776 +f 1775/1735/1776 1764/1724/1765 1763/1723/1764 +f 1764/1724/1765 1775/1735/1776 1776/1736/1777 +f 1776/1736/1777 1765/1725/1766 1764/1724/1765 +f 1766/1726/1767 1777/1737/1778 1778/1738/1779 +f 1778/1738/1779 1767/1727/1768 1766/1726/1767 +f 1767/1727/1768 1778/1738/1779 1779/1739/1780 +f 1779/1739/1780 1768/1728/1769 1767/1727/1768 +f 1768/1728/1769 1779/1739/1780 1780/1740/1781 +f 1780/1740/1781 1769/1729/1770 1768/1728/1769 +f 1769/1729/1770 1780/1740/1781 1781/1741/1782 +f 1781/1741/1782 1770/1730/1771 1769/1729/1770 +f 1770/1730/1771 1781/1741/1782 1782/1742/1783 +f 1782/1742/1783 1771/1731/1772 1770/1730/1771 +f 1771/1731/1772 1782/1742/1783 1783/1743/1784 +f 1783/1743/1784 1772/1732/1773 1771/1731/1772 +f 1772/1732/1773 1783/1743/1784 1784/1744/1785 +f 1784/1744/1785 1773/1733/1774 1772/1732/1773 +f 1773/1733/1774 1784/1744/1785 1785/1745/1786 +f 1785/1745/1786 1774/1734/1775 1773/1733/1774 +f 1774/1734/1775 1785/1745/1786 1786/1746/1787 +f 1786/1746/1787 1775/1735/1776 1774/1734/1775 +f 1775/1735/1776 1786/1746/1787 1787/1747/1788 +f 1787/1747/1788 1776/1736/1777 1775/1735/1776 +f 1777/1737/1778 1788/1748/1789 1789/1749/1790 +f 1789/1749/1790 1778/1738/1779 1777/1737/1778 +f 1778/1738/1779 1789/1749/1790 1790/1750/1791 +f 1790/1750/1791 1779/1739/1780 1778/1738/1779 +f 1779/1739/1780 1790/1750/1791 1791/1751/1792 +f 1791/1751/1792 1780/1740/1781 1779/1739/1780 +f 1780/1740/1781 1791/1751/1792 1792/1752/1793 +f 1792/1752/1793 1781/1741/1782 1780/1740/1781 +f 1781/1741/1782 1792/1752/1793 1793/1753/1794 +f 1793/1753/1794 1782/1742/1783 1781/1741/1782 +f 1782/1742/1783 1793/1753/1794 1794/1754/1795 +f 1794/1754/1795 1783/1743/1784 1782/1742/1783 +f 1783/1743/1784 1794/1754/1795 1795/1755/1796 +f 1795/1755/1796 1784/1744/1785 1783/1743/1784 +f 1784/1744/1785 1795/1755/1796 1796/1756/1797 +f 1796/1756/1797 1785/1745/1786 1784/1744/1785 +f 1785/1745/1786 1796/1756/1797 1797/1757/1798 +f 1797/1757/1798 1786/1746/1787 1785/1745/1786 +f 1786/1746/1787 1797/1757/1798 1798/1758/1799 +f 1798/1758/1799 1787/1747/1788 1786/1746/1787 +f 1788/1748/1789 1799/1759/1800 1800/1760/1801 +f 1800/1760/1801 1789/1749/1790 1788/1748/1789 +f 1789/1749/1790 1800/1760/1801 1801/1761/1802 +f 1801/1761/1802 1790/1750/1791 1789/1749/1790 +f 1790/1750/1791 1801/1761/1802 1802/1762/1803 +f 1802/1762/1803 1791/1751/1792 1790/1750/1791 +f 1791/1751/1792 1802/1762/1803 1803/1763/1804 +f 1803/1763/1804 1792/1752/1793 1791/1751/1792 +f 1792/1752/1793 1803/1763/1804 1804/1764/1805 +f 1804/1764/1805 1793/1753/1794 1792/1752/1793 +f 1793/1753/1794 1804/1764/1805 1805/1765/1806 +f 1805/1765/1806 1794/1754/1795 1793/1753/1794 +f 1794/1754/1795 1805/1765/1806 1806/1766/1807 +f 1806/1766/1807 1795/1755/1796 1794/1754/1795 +f 1795/1755/1796 1806/1766/1807 1807/1767/1808 +f 1807/1767/1808 1796/1756/1797 1795/1755/1796 +f 1796/1756/1797 1807/1767/1808 1808/1768/1809 +f 1808/1768/1809 1797/1757/1798 1796/1756/1797 +f 1797/1757/1798 1808/1768/1809 1809/1769/1810 +f 1809/1769/1810 1798/1758/1799 1797/1757/1798 +f 1799/1759/1800 1810/1770/1811 1811/1771/1812 +f 1811/1771/1812 1800/1760/1801 1799/1759/1800 +f 1800/1760/1801 1811/1771/1812 1812/1772/1813 +f 1812/1772/1813 1801/1761/1802 1800/1760/1801 +f 1801/1761/1802 1812/1772/1813 1813/1773/1814 +f 1813/1773/1814 1802/1762/1803 1801/1761/1802 +f 1802/1762/1803 1813/1773/1814 1814/1774/1815 +f 1814/1774/1815 1803/1763/1804 1802/1762/1803 +f 1803/1763/1804 1814/1774/1815 1815/1775/1816 +f 1815/1775/1816 1804/1764/1805 1803/1763/1804 +f 1804/1764/1805 1815/1775/1816 1816/1776/1817 +f 1816/1776/1817 1805/1765/1806 1804/1764/1805 +f 1805/1765/1806 1816/1776/1817 1817/1777/1818 +f 1817/1777/1818 1806/1766/1807 1805/1765/1806 +f 1806/1766/1807 1817/1777/1818 1818/1778/1819 +f 1818/1778/1819 1807/1767/1808 1806/1766/1807 +f 1807/1767/1808 1818/1778/1819 1819/1779/1820 +f 1819/1779/1820 1808/1768/1809 1807/1767/1808 +f 1808/1768/1809 1819/1779/1820 1820/1780/1821 +f 1820/1780/1821 1809/1769/1810 1808/1768/1809 +f 1810/1770/1811 1601/1562/1602 1604/1565/1605 +f 1604/1565/1605 1811/1771/1812 1810/1770/1811 +f 1811/1771/1812 1604/1565/1605 1606/1567/1607 +f 1606/1567/1607 1812/1772/1813 1811/1771/1812 +f 1812/1772/1813 1606/1567/1607 1608/1569/1609 +f 1608/1569/1609 1813/1773/1814 1812/1772/1813 +f 1813/1773/1814 1608/1569/1609 1610/1571/1611 +f 1610/1571/1611 1814/1774/1815 1813/1773/1814 +f 1814/1774/1815 1610/1571/1611 1612/1573/1613 +f 1612/1573/1613 1815/1775/1816 1814/1774/1815 +f 1815/1775/1816 1612/1573/1613 1614/1575/1615 +f 1614/1575/1615 1816/1776/1817 1815/1775/1816 +f 1816/1776/1817 1614/1575/1615 1616/1577/1617 +f 1616/1577/1617 1817/1777/1818 1816/1776/1817 +f 1817/1777/1818 1616/1577/1617 1618/1579/1619 +f 1618/1579/1619 1818/1778/1819 1817/1777/1818 +f 1818/1778/1819 1618/1579/1619 1620/1581/1621 +f 1620/1581/1621 1819/1779/1820 1818/1778/1819 +f 1819/1779/1820 1620/1581/1621 1622/1583/1623 +f 1622/1583/1623 1820/1780/1821 1819/1779/1820 +f 1622/1583/1623 1621/1582/1622 1821/1781/1822 +f 1821/1781/1822 1822/1782/1823 1622/1583/1623 +f 1822/1782/1823 1821/1781/1822 1823/1783/1824 +f 1823/1783/1824 1824/1784/1825 1822/1782/1823 +f 1824/1784/1825 1823/1783/1824 1825/1785/1826 +f 1825/1785/1826 1826/1786/1827 1824/1784/1825 +f 1826/1786/1827 1825/1785/1826 1827/1787/1828 +f 1827/1787/1828 1828/1788/1829 1826/1786/1827 +f 1828/1788/1829 1827/1787/1828 1829/1789/1830 +f 1829/1789/1830 1830/1790/1831 1828/1788/1829 +f 1830/1790/1831 1829/1789/1830 1831/1791/1832 +f 1831/1791/1832 1832/1792/1833 1830/1790/1831 +f 1832/1792/1833 1831/1791/1832 1833/1793/1834 +f 1833/1793/1834 1834/1794/1835 1832/1792/1833 +f 1834/1794/1835 1833/1793/1834 1835/1795/1836 +f 1835/1795/1836 1836/1796/1837 1834/1794/1835 +f 1836/1796/1837 1835/1795/1836 1837/1797/1838 +f 1837/1797/1838 1838/1798/1839 1836/1796/1837 +f 1838/1798/1839 1837/1797/1838 1839/1799/1840 +f 1839/1799/1840 1840/650/650 1838/1798/1839 +f 1621/1582/1622 1633/1594/1634 1841/1800/1841 +f 1841/1800/1841 1821/1781/1822 1621/1582/1622 +f 1821/1781/1822 1841/1800/1841 1842/1801/1842 +f 1842/1801/1842 1823/1783/1824 1821/1781/1822 +f 1823/1783/1824 1842/1801/1842 1843/1802/1843 +f 1843/1802/1843 1825/1785/1826 1823/1783/1824 +f 1825/1785/1826 1843/1802/1843 1844/1803/1844 +f 1844/1803/1844 1827/1787/1828 1825/1785/1826 +f 1827/1787/1828 1844/1803/1844 1845/1804/1845 +f 1845/1804/1845 1829/1789/1830 1827/1787/1828 +f 1829/1789/1830 1845/1804/1845 1846/1805/1846 +f 1846/1805/1846 1831/1791/1832 1829/1789/1830 +f 1831/1791/1832 1846/1805/1846 1847/1806/1847 +f 1847/1806/1847 1833/1793/1834 1831/1791/1832 +f 1833/1793/1834 1847/1806/1847 1848/1807/1848 +f 1848/1807/1848 1835/1795/1836 1833/1793/1834 +f 1835/1795/1836 1848/1807/1848 1849/1808/1849 +f 1849/1808/1849 1837/1797/1838 1835/1795/1836 +f 1837/1797/1838 1849/1808/1849 1850/1809/1850 +f 1850/1809/1850 1839/1799/1840 1837/1797/1838 +f 1633/1594/1634 1644/1605/1645 1851/1810/1851 +f 1851/1810/1851 1841/1800/1841 1633/1594/1634 +f 1841/1800/1841 1851/1810/1851 1852/1811/1852 +f 1852/1811/1852 1842/1801/1842 1841/1800/1841 +f 1842/1801/1842 1852/1811/1852 1853/1812/1853 +f 1853/1812/1853 1843/1802/1843 1842/1801/1842 +f 1843/1802/1843 1853/1812/1853 1854/1813/1854 +f 1854/1813/1854 1844/1803/1844 1843/1802/1843 +f 1844/1803/1844 1854/1813/1854 1855/1814/1855 +f 1855/1814/1855 1845/1804/1845 1844/1803/1844 +f 1845/1804/1845 1855/1814/1855 1856/1815/1856 +f 1856/1815/1856 1846/1805/1846 1845/1804/1845 +f 1846/1805/1846 1856/1815/1856 1857/1816/1857 +f 1857/1816/1857 1847/1806/1847 1846/1805/1846 +f 1847/1806/1847 1857/1816/1857 1858/1817/1858 +f 1858/1817/1858 1848/1807/1848 1847/1806/1847 +f 1848/1807/1848 1858/1817/1858 1859/1818/1859 +f 1859/1818/1859 1849/1808/1849 1848/1807/1848 +f 1849/1808/1849 1859/1818/1859 1860/1819/1860 +f 1860/1819/1860 1850/1809/1850 1849/1808/1849 +f 1644/1605/1645 1655/1616/1656 1861/1820/1861 +f 1861/1820/1861 1851/1810/1851 1644/1605/1645 +f 1851/1810/1851 1861/1820/1861 1862/1821/1862 +f 1862/1821/1862 1852/1811/1852 1851/1810/1851 +f 1852/1811/1852 1862/1821/1862 1863/1822/1863 +f 1863/1822/1863 1853/1812/1853 1852/1811/1852 +f 1853/1812/1853 1863/1822/1863 1864/1823/1864 +f 1864/1823/1864 1854/1813/1854 1853/1812/1853 +f 1854/1813/1854 1864/1823/1864 1865/1824/1865 +f 1865/1824/1865 1855/1814/1855 1854/1813/1854 +f 1855/1814/1855 1865/1824/1865 1866/1825/1866 +f 1866/1825/1866 1856/1815/1856 1855/1814/1855 +f 1856/1815/1856 1866/1825/1866 1867/1826/1867 +f 1867/1826/1867 1857/1816/1857 1856/1815/1856 +f 1857/1816/1857 1867/1826/1867 1868/1827/1868 +f 1868/1827/1868 1858/1817/1858 1857/1816/1857 +f 1858/1817/1858 1868/1827/1868 1869/1828/1869 +f 1869/1828/1869 1859/1818/1859 1858/1817/1858 +f 1859/1818/1859 1869/1828/1869 1870/1829/1870 +f 1870/1829/1870 1860/1819/1860 1859/1818/1859 +f 1655/1616/1656 1666/1627/1667 1871/1830/1871 +f 1871/1830/1871 1861/1820/1861 1655/1616/1656 +f 1861/1820/1861 1871/1830/1871 1872/1831/1872 +f 1872/1831/1872 1862/1821/1862 1861/1820/1861 +f 1862/1821/1862 1872/1831/1872 1873/1832/1873 +f 1873/1832/1873 1863/1822/1863 1862/1821/1862 +f 1863/1822/1863 1873/1832/1873 1874/1833/1874 +f 1874/1833/1874 1864/1823/1864 1863/1822/1863 +f 1864/1823/1864 1874/1833/1874 1875/1834/1875 +f 1875/1834/1875 1865/1824/1865 1864/1823/1864 +f 1865/1824/1865 1875/1834/1875 1876/1835/1876 +f 1876/1835/1876 1866/1825/1866 1865/1824/1865 +f 1866/1825/1866 1876/1835/1876 1877/1836/1877 +f 1877/1836/1877 1867/1826/1867 1866/1825/1866 +f 1867/1826/1867 1877/1836/1877 1878/1837/1878 +f 1878/1837/1878 1868/1827/1868 1867/1826/1867 +f 1868/1827/1868 1878/1837/1878 1879/1838/1879 +f 1879/1838/1879 1869/1828/1869 1868/1827/1868 +f 1869/1828/1869 1879/1838/1879 1880/1839/1880 +f 1880/1839/1880 1870/1829/1870 1869/1828/1869 +f 1666/1627/1667 1677/1638/1678 1881/1840/1881 +f 1881/1840/1881 1871/1830/1871 1666/1627/1667 +f 1871/1830/1871 1881/1840/1881 1882/1841/1882 +f 1882/1841/1882 1872/1831/1872 1871/1830/1871 +f 1872/1831/1872 1882/1841/1882 1883/1842/1883 +f 1883/1842/1883 1873/1832/1873 1872/1831/1872 +f 1873/1832/1873 1883/1842/1883 1884/1843/1884 +f 1884/1843/1884 1874/1833/1874 1873/1832/1873 +f 1874/1833/1874 1884/1843/1884 1885/1844/1885 +f 1885/1844/1885 1875/1834/1875 1874/1833/1874 +f 1875/1834/1875 1885/1844/1885 1886/1845/1886 +f 1886/1845/1886 1876/1835/1876 1875/1834/1875 +f 1876/1835/1876 1886/1845/1886 1887/1846/1887 +f 1887/1846/1887 1877/1836/1877 1876/1835/1876 +f 1877/1836/1877 1887/1846/1887 1888/1847/1888 +f 1888/1847/1888 1878/1837/1878 1877/1836/1877 +f 1878/1837/1878 1888/1847/1888 1889/1848/1889 +f 1889/1848/1889 1879/1838/1879 1878/1837/1878 +f 1879/1838/1879 1889/1848/1889 1890/1849/1890 +f 1890/1849/1890 1880/1839/1880 1879/1838/1879 +f 1677/1638/1678 1688/1649/1689 1891/1850/1891 +f 1891/1850/1891 1881/1840/1881 1677/1638/1678 +f 1881/1840/1881 1891/1850/1891 1892/1851/1892 +f 1892/1851/1892 1882/1841/1882 1881/1840/1881 +f 1882/1841/1882 1892/1851/1892 1893/1852/1893 +f 1893/1852/1893 1883/1842/1883 1882/1841/1882 +f 1883/1842/1883 1893/1852/1893 1894/1853/1894 +f 1894/1853/1894 1884/1843/1884 1883/1842/1883 +f 1884/1843/1884 1894/1853/1894 1895/1854/1895 +f 1895/1854/1895 1885/1844/1885 1884/1843/1884 +f 1885/1844/1885 1895/1854/1895 1896/1855/1896 +f 1896/1855/1896 1886/1845/1886 1885/1844/1885 +f 1886/1845/1886 1896/1855/1896 1897/1856/1897 +f 1897/1856/1897 1887/1846/1887 1886/1845/1886 +f 1887/1846/1887 1897/1856/1897 1898/1857/1898 +f 1898/1857/1898 1888/1847/1888 1887/1846/1887 +f 1888/1847/1888 1898/1857/1898 1899/1858/1899 +f 1899/1858/1899 1889/1848/1889 1888/1847/1888 +f 1889/1848/1889 1899/1858/1899 1900/1859/1900 +f 1900/1859/1900 1890/1849/1890 1889/1848/1889 +f 1688/1649/1689 1699/1660/1700 1901/1860/1901 +f 1901/1860/1901 1891/1850/1891 1688/1649/1689 +f 1891/1850/1891 1901/1860/1901 1902/1861/1902 +f 1902/1861/1902 1892/1851/1892 1891/1850/1891 +f 1892/1851/1892 1902/1861/1902 1903/1862/1903 +f 1903/1862/1903 1893/1852/1893 1892/1851/1892 +f 1893/1852/1893 1903/1862/1903 1904/1863/1904 +f 1904/1863/1904 1894/1853/1894 1893/1852/1893 +f 1894/1853/1894 1904/1863/1904 1905/1864/1905 +f 1905/1864/1905 1895/1854/1895 1894/1853/1894 +f 1895/1854/1895 1905/1864/1905 1906/1865/1906 +f 1906/1865/1906 1896/1855/1896 1895/1854/1895 +f 1896/1855/1896 1906/1865/1906 1907/1866/1907 +f 1907/1866/1907 1897/1856/1897 1896/1855/1896 +f 1897/1856/1897 1907/1866/1907 1908/1867/1908 +f 1908/1867/1908 1898/1857/1898 1897/1856/1897 +f 1898/1857/1898 1908/1867/1908 1909/1868/1909 +f 1909/1868/1909 1899/1858/1899 1898/1857/1898 +f 1899/1858/1899 1909/1868/1909 1910/1869/1910 +f 1910/1869/1910 1900/1859/1900 1899/1858/1899 +f 1699/1660/1700 1710/1671/1711 1911/1870/1911 +f 1911/1870/1911 1901/1860/1901 1699/1660/1700 +f 1901/1860/1901 1911/1870/1911 1912/1871/1912 +f 1912/1871/1912 1902/1861/1902 1901/1860/1901 +f 1902/1861/1902 1912/1871/1912 1913/1872/1913 +f 1913/1872/1913 1903/1862/1903 1902/1861/1902 +f 1903/1862/1903 1913/1872/1913 1914/1873/1914 +f 1914/1873/1914 1904/1863/1904 1903/1862/1903 +f 1904/1863/1904 1914/1873/1914 1915/1874/1915 +f 1915/1874/1915 1905/1864/1905 1904/1863/1904 +f 1905/1864/1905 1915/1874/1915 1916/1875/1916 +f 1916/1875/1916 1906/1865/1906 1905/1864/1905 +f 1906/1865/1906 1916/1875/1916 1917/1876/1917 +f 1917/1876/1917 1907/1866/1907 1906/1865/1906 +f 1907/1866/1907 1917/1876/1917 1918/1877/1918 +f 1918/1877/1918 1908/1867/1908 1907/1866/1907 +f 1908/1867/1908 1918/1877/1918 1919/1878/1919 +f 1919/1878/1919 1909/1868/1909 1908/1867/1908 +f 1909/1868/1909 1919/1878/1919 1920/1879/1920 +f 1920/1879/1920 1910/1869/1910 1909/1868/1909 +f 1710/1671/1711 1721/1681/1722 1921/1880/1921 +f 1921/1880/1921 1911/1870/1911 1710/1671/1711 +f 1911/1870/1911 1921/1880/1921 1922/1881/1922 +f 1922/1881/1922 1912/1871/1912 1911/1870/1911 +f 1912/1871/1912 1922/1881/1922 1923/1882/1923 +f 1923/1882/1923 1913/1872/1913 1912/1871/1912 +f 1913/1872/1913 1923/1882/1923 1924/1883/1924 +f 1924/1883/1924 1914/1873/1914 1913/1872/1913 +f 1914/1873/1914 1924/1883/1924 1925/1884/1925 +f 1925/1884/1925 1915/1874/1915 1914/1873/1914 +f 1915/1874/1915 1925/1884/1925 1926/1885/1926 +f 1926/1885/1926 1916/1875/1916 1915/1874/1915 +f 1916/1875/1916 1926/1885/1926 1927/1886/1927 +f 1927/1886/1927 1917/1876/1917 1916/1875/1916 +f 1917/1876/1917 1927/1886/1927 1928/1887/1928 +f 1928/1887/1928 1918/1877/1918 1917/1876/1917 +f 1918/1877/1918 1928/1887/1928 1929/1888/1929 +f 1929/1888/1929 1919/1878/1919 1918/1877/1918 +f 1919/1878/1919 1929/1888/1929 1930/1889/1930 +f 1930/1889/1930 1920/1879/1920 1919/1878/1919 +f 1721/1681/1722 1732/1692/1733 1931/1890/1931 +f 1931/1890/1931 1921/1880/1921 1721/1681/1722 +f 1921/1880/1921 1931/1890/1931 1932/1891/1932 +f 1932/1891/1932 1922/1881/1922 1921/1880/1921 +f 1922/1881/1922 1932/1891/1932 1933/1892/1933 +f 1933/1892/1933 1923/1882/1923 1922/1881/1922 +f 1923/1882/1923 1933/1892/1933 1934/1893/1934 +f 1934/1893/1934 1924/1883/1924 1923/1882/1923 +f 1924/1883/1924 1934/1893/1934 1935/1894/1935 +f 1935/1894/1935 1925/1884/1925 1924/1883/1924 +f 1925/1884/1925 1935/1894/1935 1936/1895/1936 +f 1936/1895/1936 1926/1885/1926 1925/1884/1925 +f 1926/1885/1926 1936/1895/1936 1937/1896/1937 +f 1937/1896/1937 1927/1886/1927 1926/1885/1926 +f 1927/1886/1927 1937/1896/1937 1938/1897/1938 +f 1938/1897/1938 1928/1887/1928 1927/1886/1927 +f 1928/1887/1928 1938/1897/1938 1939/1898/1939 +f 1939/1898/1939 1929/1888/1929 1928/1887/1928 +f 1929/1888/1929 1939/1898/1939 1940/1899/1940 +f 1940/1899/1940 1930/1889/1930 1929/1888/1929 +f 1732/1692/1733 1743/1703/1744 1941/1900/1941 +f 1941/1900/1941 1931/1890/1931 1732/1692/1733 +f 1931/1890/1931 1941/1900/1941 1942/1901/1942 +f 1942/1901/1942 1932/1891/1932 1931/1890/1931 +f 1932/1891/1932 1942/1901/1942 1943/1902/1943 +f 1943/1902/1943 1933/1892/1933 1932/1891/1932 +f 1933/1892/1933 1943/1902/1943 1944/1903/1944 +f 1944/1903/1944 1934/1893/1934 1933/1892/1933 +f 1934/1893/1934 1944/1903/1944 1945/1904/1945 +f 1945/1904/1945 1935/1894/1935 1934/1893/1934 +f 1935/1894/1935 1945/1904/1945 1946/1905/1946 +f 1946/1905/1946 1936/1895/1936 1935/1894/1935 +f 1936/1895/1936 1946/1905/1946 1947/1906/1947 +f 1947/1906/1947 1937/1896/1937 1936/1895/1936 +f 1937/1896/1937 1947/1906/1947 1948/1907/1948 +f 1948/1907/1948 1938/1897/1938 1937/1896/1937 +f 1938/1897/1938 1948/1907/1948 1949/1908/1949 +f 1949/1908/1949 1939/1898/1939 1938/1897/1938 +f 1939/1898/1939 1949/1908/1949 1950/1909/1950 +f 1950/1909/1950 1940/1899/1940 1939/1898/1939 +f 1743/1703/1744 1754/1714/1755 1951/1910/1951 +f 1951/1910/1951 1941/1900/1941 1743/1703/1744 +f 1941/1900/1941 1951/1910/1951 1952/1911/1952 +f 1952/1911/1952 1942/1901/1942 1941/1900/1941 +f 1942/1901/1942 1952/1911/1952 1953/1912/1953 +f 1953/1912/1953 1943/1902/1943 1942/1901/1942 +f 1943/1902/1943 1953/1912/1953 1954/1913/1954 +f 1954/1913/1954 1944/1903/1944 1943/1902/1943 +f 1944/1903/1944 1954/1913/1954 1955/1914/1955 +f 1955/1914/1955 1945/1904/1945 1944/1903/1944 +f 1945/1904/1945 1955/1914/1955 1956/1915/1956 +f 1956/1915/1956 1946/1905/1946 1945/1904/1945 +f 1946/1905/1946 1956/1915/1956 1957/1916/1957 +f 1957/1916/1957 1947/1906/1947 1946/1905/1946 +f 1947/1906/1947 1957/1916/1957 1958/1917/1958 +f 1958/1917/1958 1948/1907/1948 1947/1906/1947 +f 1948/1907/1948 1958/1917/1958 1959/1918/1959 +f 1959/1918/1959 1949/1908/1949 1948/1907/1948 +f 1949/1908/1949 1959/1918/1959 1960/1919/1960 +f 1960/1919/1960 1950/1909/1950 1949/1908/1949 +f 1754/1714/1755 1765/1725/1766 1961/1920/1961 +f 1961/1920/1961 1951/1910/1951 1754/1714/1755 +f 1951/1910/1951 1961/1920/1961 1962/1921/1962 +f 1962/1921/1962 1952/1911/1952 1951/1910/1951 +f 1952/1911/1952 1962/1921/1962 1963/1922/1963 +f 1963/1922/1963 1953/1912/1953 1952/1911/1952 +f 1953/1912/1953 1963/1922/1963 1964/1923/1964 +f 1964/1923/1964 1954/1913/1954 1953/1912/1953 +f 1954/1913/1954 1964/1923/1964 1965/1924/1965 +f 1965/1924/1965 1955/1914/1955 1954/1913/1954 +f 1955/1914/1955 1965/1924/1965 1966/1925/1966 +f 1966/1925/1966 1956/1915/1956 1955/1914/1955 +f 1956/1915/1956 1966/1925/1966 1967/1926/1967 +f 1967/1926/1967 1957/1916/1957 1956/1915/1956 +f 1957/1916/1957 1967/1926/1967 1968/1927/1968 +f 1968/1927/1968 1958/1917/1958 1957/1916/1957 +f 1958/1917/1958 1968/1927/1968 1969/1928/1969 +f 1969/1928/1969 1959/1918/1959 1958/1917/1958 +f 1959/1918/1959 1969/1928/1969 1970/1929/1970 +f 1970/1929/1970 1960/1919/1960 1959/1918/1959 +f 1765/1725/1766 1776/1736/1777 1971/1930/1971 +f 1971/1930/1971 1961/1920/1961 1765/1725/1766 +f 1961/1920/1961 1971/1930/1971 1972/1931/1972 +f 1972/1931/1972 1962/1921/1962 1961/1920/1961 +f 1962/1921/1962 1972/1931/1972 1973/1932/1973 +f 1973/1932/1973 1963/1922/1963 1962/1921/1962 +f 1963/1922/1963 1973/1932/1973 1974/1933/1974 +f 1974/1933/1974 1964/1923/1964 1963/1922/1963 +f 1964/1923/1964 1974/1933/1974 1975/1934/1975 +f 1975/1934/1975 1965/1924/1965 1964/1923/1964 +f 1965/1924/1965 1975/1934/1975 1976/1935/1976 +f 1976/1935/1976 1966/1925/1966 1965/1924/1965 +f 1966/1925/1966 1976/1935/1976 1977/1936/1977 +f 1977/1936/1977 1967/1926/1967 1966/1925/1966 +f 1967/1926/1967 1977/1936/1977 1978/1937/1978 +f 1978/1937/1978 1968/1927/1968 1967/1926/1967 +f 1968/1927/1968 1978/1937/1978 1979/1938/1979 +f 1979/1938/1979 1969/1928/1969 1968/1927/1968 +f 1969/1928/1969 1979/1938/1979 1980/1939/1980 +f 1980/1939/1980 1970/1929/1970 1969/1928/1969 +f 1776/1736/1777 1787/1747/1788 1981/1940/1981 +f 1981/1940/1981 1971/1930/1971 1776/1736/1777 +f 1971/1930/1971 1981/1940/1981 1982/1941/1982 +f 1982/1941/1982 1972/1931/1972 1971/1930/1971 +f 1972/1931/1972 1982/1941/1982 1983/1942/1983 +f 1983/1942/1983 1973/1932/1973 1972/1931/1972 +f 1973/1932/1973 1983/1942/1983 1984/1943/1984 +f 1984/1943/1984 1974/1933/1974 1973/1932/1973 +f 1974/1933/1974 1984/1943/1984 1985/1944/1985 +f 1985/1944/1985 1975/1934/1975 1974/1933/1974 +f 1975/1934/1975 1985/1944/1985 1986/1945/1986 +f 1986/1945/1986 1976/1935/1976 1975/1934/1975 +f 1976/1935/1976 1986/1945/1986 1987/1946/1987 +f 1987/1946/1987 1977/1936/1977 1976/1935/1976 +f 1977/1936/1977 1987/1946/1987 1988/1947/1988 +f 1988/1947/1988 1978/1937/1978 1977/1936/1977 +f 1978/1937/1978 1988/1947/1988 1989/1948/1989 +f 1989/1948/1989 1979/1938/1979 1978/1937/1978 +f 1979/1938/1979 1989/1948/1989 1990/1949/1990 +f 1990/1949/1990 1980/1939/1980 1979/1938/1979 +f 1787/1747/1788 1798/1758/1799 1991/1950/1991 +f 1991/1950/1991 1981/1940/1981 1787/1747/1788 +f 1981/1940/1981 1991/1950/1991 1992/1951/1992 +f 1992/1951/1992 1982/1941/1982 1981/1940/1981 +f 1982/1941/1982 1992/1951/1992 1993/1952/1993 +f 1993/1952/1993 1983/1942/1983 1982/1941/1982 +f 1983/1942/1983 1993/1952/1993 1994/1953/1994 +f 1994/1953/1994 1984/1943/1984 1983/1942/1983 +f 1984/1943/1984 1994/1953/1994 1995/1954/1995 +f 1995/1954/1995 1985/1944/1985 1984/1943/1984 +f 1985/1944/1985 1995/1954/1995 1996/1955/1996 +f 1996/1955/1996 1986/1945/1986 1985/1944/1985 +f 1986/1945/1986 1996/1955/1996 1997/1956/1997 +f 1997/1956/1997 1987/1946/1987 1986/1945/1986 +f 1987/1946/1987 1997/1956/1997 1998/1957/1998 +f 1998/1957/1998 1988/1947/1988 1987/1946/1987 +f 1988/1947/1988 1998/1957/1998 1999/1958/1999 +f 1999/1958/1999 1989/1948/1989 1988/1947/1988 +f 1989/1948/1989 1999/1958/1999 2000/1959/2000 +f 2000/1959/2000 1990/1949/1990 1989/1948/1989 +f 1798/1758/1799 1809/1769/1810 2001/1960/2001 +f 2001/1960/2001 1991/1950/1991 1798/1758/1799 +f 1991/1950/1991 2001/1960/2001 2002/1961/2002 +f 2002/1961/2002 1992/1951/1992 1991/1950/1991 +f 1992/1951/1992 2002/1961/2002 2003/1962/2003 +f 2003/1962/2003 1993/1952/1993 1992/1951/1992 +f 1993/1952/1993 2003/1962/2003 2004/1963/2004 +f 2004/1963/2004 1994/1953/1994 1993/1952/1993 +f 1994/1953/1994 2004/1963/2004 2005/1964/2005 +f 2005/1964/2005 1995/1954/1995 1994/1953/1994 +f 1995/1954/1995 2005/1964/2005 2006/1965/2006 +f 2006/1965/2006 1996/1955/1996 1995/1954/1995 +f 1996/1955/1996 2006/1965/2006 2007/1966/2007 +f 2007/1966/2007 1997/1956/1997 1996/1955/1996 +f 1997/1956/1997 2007/1966/2007 2008/1967/2008 +f 2008/1967/2008 1998/1957/1998 1997/1956/1997 +f 1998/1957/1998 2008/1967/2008 2009/1968/2009 +f 2009/1968/2009 1999/1958/1999 1998/1957/1998 +f 1999/1958/1999 2009/1968/2009 2010/1969/2010 +f 2010/1969/2010 2000/1959/2000 1999/1958/1999 +f 1809/1769/1810 1820/1780/1821 2011/1970/2011 +f 2011/1970/2011 2001/1960/2001 1809/1769/1810 +f 2001/1960/2001 2011/1970/2011 2012/1971/2012 +f 2012/1971/2012 2002/1961/2002 2001/1960/2001 +f 2002/1961/2002 2012/1971/2012 2013/1972/2013 +f 2013/1972/2013 2003/1962/2003 2002/1961/2002 +f 2003/1962/2003 2013/1972/2013 2014/1973/2014 +f 2014/1973/2014 2004/1963/2004 2003/1962/2003 +f 2004/1963/2004 2014/1973/2014 2015/1974/2015 +f 2015/1974/2015 2005/1964/2005 2004/1963/2004 +f 2005/1964/2005 2015/1974/2015 2016/1975/2016 +f 2016/1975/2016 2006/1965/2006 2005/1964/2005 +f 2006/1965/2006 2016/1975/2016 2017/1976/2017 +f 2017/1976/2017 2007/1966/2007 2006/1965/2006 +f 2007/1966/2007 2017/1976/2017 2018/1977/2018 +f 2018/1977/2018 2008/1967/2008 2007/1966/2007 +f 2008/1967/2008 2018/1977/2018 2019/1978/2019 +f 2019/1978/2019 2009/1968/2009 2008/1967/2008 +f 2009/1968/2009 2019/1978/2019 2020/1979/2020 +f 2020/1979/2020 2010/1969/2010 2009/1968/2009 +f 1820/1780/1821 1622/1583/1623 1822/1782/1823 +f 1822/1782/1823 2011/1970/2011 1820/1780/1821 +f 2011/1970/2011 1822/1782/1823 1824/1784/1825 +f 1824/1784/1825 2012/1971/2012 2011/1970/2011 +f 2012/1971/2012 1824/1784/1825 1826/1786/1827 +f 1826/1786/1827 2013/1972/2013 2012/1971/2012 +f 2013/1972/2013 1826/1786/1827 1828/1788/1829 +f 1828/1788/1829 2014/1973/2014 2013/1972/2013 +f 2014/1973/2014 1828/1788/1829 1830/1790/1831 +f 1830/1790/1831 2015/1974/2015 2014/1973/2014 +f 2015/1974/2015 1830/1790/1831 1832/1792/1833 +f 1832/1792/1833 2016/1975/2016 2015/1974/2015 +f 2016/1975/2016 1832/1792/1833 1834/1794/1835 +f 1834/1794/1835 2017/1976/2017 2016/1975/2016 +f 2017/1976/2017 1834/1794/1835 1836/1796/1837 +f 1836/1796/1837 2018/1977/2018 2017/1976/2017 +f 2018/1977/2018 1836/1796/1837 1838/1798/1839 +f 1838/1798/1839 2019/1978/2019 2018/1977/2018 +f 2019/1978/2019 1838/1798/1839 1840/650/650 +f 1840/650/650 2020/1979/2020 2019/1978/2019 +f 2021/1980/2021 2022/1981/2022 2023/1982/2023 +f 2023/1982/2023 2024/1983/2024 2021/1980/2021 +f 2024/1983/2024 2023/1982/2023 2025/1984/2025 +f 2025/1984/2025 2026/1985/2026 2024/1983/2024 +f 2026/1985/2026 2025/1984/2025 2027/1986/2027 +f 2027/1986/2027 2028/1987/2028 2026/1985/2026 +f 2028/1987/2028 2027/1986/2027 2029/1988/2029 +f 2029/1988/2029 2030/1989/2030 2028/1987/2028 +f 2030/1989/2030 2029/1988/2029 2031/1990/2031 +f 2031/1990/2031 2032/1991/2032 2030/1989/2030 +f 2032/1991/2032 2031/1990/2031 2033/1992/2033 +f 2033/1992/2033 2034/1993/2034 2032/1991/2032 +f 2034/1993/2034 2033/1992/2033 2035/1994/2035 +f 2035/1994/2035 2036/1995/2036 2034/1993/2034 +f 2036/1995/2036 2035/1994/2035 2037/1996/2037 +f 2037/1996/2037 2038/1997/2038 2036/1995/2036 +f 2038/1997/2038 2037/1996/2037 2039/1998/2039 +f 2039/1998/2039 2040/1999/2040 2038/1997/2038 +f 2040/1999/2040 2039/1998/2039 2041/2000/2041 +f 2041/2000/2041 2042/2001/2042 2040/1999/2040 +f 2022/1981/2022 2043/2002/2043 2044/2003/2044 +f 2044/2003/2044 2023/1982/2023 2022/1981/2022 +f 2023/1982/2023 2044/2003/2044 2045/2004/2045 +f 2045/2004/2045 2025/1984/2025 2023/1982/2023 +f 2025/1984/2025 2045/2004/2045 2046/2005/2046 +f 2046/2005/2046 2027/1986/2027 2025/1984/2025 +f 2027/1986/2027 2046/2005/2046 2047/2006/2047 +f 2047/2006/2047 2029/1988/2029 2027/1986/2027 +f 2029/1988/2029 2047/2006/2047 2048/2007/2048 +f 2048/2007/2048 2031/1990/2031 2029/1988/2029 +f 2031/1990/2031 2048/2007/2048 2049/2008/2049 +f 2049/2008/2049 2033/1992/2033 2031/1990/2031 +f 2033/1992/2033 2049/2008/2049 2050/2009/2050 +f 2050/2009/2050 2035/1994/2035 2033/1992/2033 +f 2035/1994/2035 2050/2009/2050 2051/2010/2051 +f 2051/2010/2051 2037/1996/2037 2035/1994/2035 +f 2037/1996/2037 2051/2010/2051 2052/2011/2052 +f 2052/2011/2052 2039/1998/2039 2037/1996/2037 +f 2039/1998/2039 2052/2011/2052 2053/2012/2053 +f 2053/2012/2053 2041/2000/2041 2039/1998/2039 +f 2043/2002/2043 2054/2013/2054 2055/2014/2055 +f 2055/2014/2055 2044/2003/2044 2043/2002/2043 +f 2044/2003/2044 2055/2014/2055 2056/2015/2056 +f 2056/2015/2056 2045/2004/2045 2044/2003/2044 +f 2045/2004/2045 2056/2015/2056 2057/2016/2057 +f 2057/2016/2057 2046/2005/2046 2045/2004/2045 +f 2046/2005/2046 2057/2016/2057 2058/2017/2058 +f 2058/2017/2058 2047/2006/2047 2046/2005/2046 +f 2047/2006/2047 2058/2017/2058 2059/2018/2059 +f 2059/2018/2059 2048/2007/2048 2047/2006/2047 +f 2048/2007/2048 2059/2018/2059 2060/2019/2060 +f 2060/2019/2060 2049/2008/2049 2048/2007/2048 +f 2049/2008/2049 2060/2019/2060 2061/2020/2061 +f 2061/2020/2061 2050/2009/2050 2049/2008/2049 +f 2050/2009/2050 2061/2020/2061 2062/2021/2062 +f 2062/2021/2062 2051/2010/2051 2050/2009/2050 +f 2051/2010/2051 2062/2021/2062 2063/2022/2063 +f 2063/2022/2063 2052/2011/2052 2051/2010/2051 +f 2052/2011/2052 2063/2022/2063 2064/2023/2064 +f 2064/2023/2064 2053/2012/2053 2052/2011/2052 +f 2054/2013/2054 2065/2024/2065 2066/2025/2066 +f 2066/2025/2066 2055/2014/2055 2054/2013/2054 +f 2055/2014/2055 2066/2025/2066 2067/2026/2067 +f 2067/2026/2067 2056/2015/2056 2055/2014/2055 +f 2056/2015/2056 2067/2026/2067 2068/2027/2068 +f 2068/2027/2068 2057/2016/2057 2056/2015/2056 +f 2057/2016/2057 2068/2027/2068 2069/2028/2069 +f 2069/2028/2069 2058/2017/2058 2057/2016/2057 +f 2058/2017/2058 2069/2028/2069 2070/2029/2070 +f 2070/2029/2070 2059/2018/2059 2058/2017/2058 +f 2059/2018/2059 2070/2029/2070 2071/2030/2071 +f 2071/2030/2071 2060/2019/2060 2059/2018/2059 +f 2060/2019/2060 2071/2030/2071 2072/2031/2072 +f 2072/2031/2072 2061/2020/2061 2060/2019/2060 +f 2061/2020/2061 2072/2031/2072 2073/2032/2073 +f 2073/2032/2073 2062/2021/2062 2061/2020/2061 +f 2062/2021/2062 2073/2032/2073 2074/2033/2074 +f 2074/2033/2074 2063/2022/2063 2062/2021/2062 +f 2063/2022/2063 2074/2033/2074 2075/2034/2075 +f 2075/2034/2075 2064/2023/2064 2063/2022/2063 +f 2065/2024/2065 2076/2035/2076 2077/2036/2077 +f 2077/2036/2077 2066/2025/2066 2065/2024/2065 +f 2066/2025/2066 2077/2036/2077 2078/2037/2078 +f 2078/2037/2078 2067/2026/2067 2066/2025/2066 +f 2067/2026/2067 2078/2037/2078 2079/2038/2079 +f 2079/2038/2079 2068/2027/2068 2067/2026/2067 +f 2068/2027/2068 2079/2038/2079 2080/2039/2080 +f 2080/2039/2080 2069/2028/2069 2068/2027/2068 +f 2069/2028/2069 2080/2039/2080 2081/2040/2081 +f 2081/2040/2081 2070/2029/2070 2069/2028/2069 +f 2070/2029/2070 2081/2040/2081 2082/2041/2082 +f 2082/2041/2082 2071/2030/2071 2070/2029/2070 +f 2071/2030/2071 2082/2041/2082 2083/2042/2083 +f 2083/2042/2083 2072/2031/2072 2071/2030/2071 +f 2072/2031/2072 2083/2042/2083 2084/2043/2084 +f 2084/2043/2084 2073/2032/2073 2072/2031/2072 +f 2073/2032/2073 2084/2043/2084 2085/2044/2085 +f 2085/2044/2085 2074/2033/2074 2073/2032/2073 +f 2074/2033/2074 2085/2044/2085 2086/2045/2086 +f 2086/2045/2086 2075/2034/2075 2074/2033/2074 +f 2076/2035/2076 2087/2024/2087 2088/2046/2088 +f 2088/2046/2088 2077/2036/2077 2076/2035/2076 +f 2077/2036/2077 2088/2046/2088 2089/2047/2089 +f 2089/2047/2089 2078/2037/2078 2077/2036/2077 +f 2078/2037/2078 2089/2047/2089 2090/2048/2090 +f 2090/2048/2090 2079/2038/2079 2078/2037/2078 +f 2079/2038/2079 2090/2048/2090 2091/2049/2091 +f 2091/2049/2091 2080/2039/2080 2079/2038/2079 +f 2080/2039/2080 2091/2049/2091 2092/2050/2092 +f 2092/2050/2092 2081/2040/2081 2080/2039/2080 +f 2081/2040/2081 2092/2050/2092 2093/2051/2093 +f 2093/2051/2093 2082/2041/2082 2081/2040/2081 +f 2082/2041/2082 2093/2051/2093 2094/2052/2094 +f 2094/2052/2094 2083/2042/2083 2082/2041/2082 +f 2083/2042/2083 2094/2052/2094 2095/2053/2095 +f 2095/2053/2095 2084/2043/2084 2083/2042/2083 +f 2084/2043/2084 2095/2053/2095 2096/2054/2096 +f 2096/2054/2096 2085/2044/2085 2084/2043/2084 +f 2085/2044/2085 2096/2054/2096 2097/2055/2097 +f 2097/2055/2097 2086/2045/2086 2085/2044/2085 +f 2087/2024/2087 2098/2013/2098 2099/2056/2099 +f 2099/2056/2099 2088/2046/2088 2087/2024/2087 +f 2088/2046/2088 2099/2056/2099 2100/2057/2100 +f 2100/2057/2100 2089/2047/2089 2088/2046/2088 +f 2089/2047/2089 2100/2057/2100 2101/2058/2101 +f 2101/2058/2101 2090/2048/2090 2089/2047/2089 +f 2090/2048/2090 2101/2058/2101 2102/2059/2102 +f 2102/2059/2102 2091/2049/2091 2090/2048/2090 +f 2091/2049/2091 2102/2059/2102 2103/2060/2103 +f 2103/2060/2103 2092/2050/2092 2091/2049/2091 +f 2092/2050/2092 2103/2060/2103 2104/2061/2104 +f 2104/2061/2104 2093/2051/2093 2092/2050/2092 +f 2093/2051/2093 2104/2061/2104 2105/2062/2105 +f 2105/2062/2105 2094/2052/2094 2093/2051/2093 +f 2094/2052/2094 2105/2062/2105 2106/2063/2106 +f 2106/2063/2106 2095/2053/2095 2094/2052/2094 +f 2095/2053/2095 2106/2063/2106 2107/2064/2107 +f 2107/2064/2107 2096/2054/2096 2095/2053/2095 +f 2096/2054/2096 2107/2064/2107 2108/2065/2108 +f 2108/2065/2108 2097/2055/2097 2096/2054/2096 +f 2098/2013/2098 2109/2002/2109 2110/2066/2110 +f 2110/2066/2110 2099/2056/2099 2098/2013/2098 +f 2099/2056/2099 2110/2066/2110 2111/2067/2111 +f 2111/2067/2111 2100/2057/2100 2099/2056/2099 +f 2100/2057/2100 2111/2067/2111 2112/2068/2112 +f 2112/2068/2112 2101/2058/2101 2100/2057/2100 +f 2101/2058/2101 2112/2068/2112 2113/2069/2113 +f 2113/2069/2113 2102/2059/2102 2101/2058/2101 +f 2102/2059/2102 2113/2069/2113 2114/2070/2114 +f 2114/2070/2114 2103/2060/2103 2102/2059/2102 +f 2103/2060/2103 2114/2070/2114 2115/2071/2115 +f 2115/2071/2115 2104/2061/2104 2103/2060/2103 +f 2104/2061/2104 2115/2071/2115 2116/2072/2116 +f 2116/2072/2116 2105/2062/2105 2104/2061/2104 +f 2105/2062/2105 2116/2072/2116 2117/2073/2117 +f 2117/2073/2117 2106/2063/2106 2105/2062/2105 +f 2106/2063/2106 2117/2073/2117 2118/2074/2118 +f 2118/2074/2118 2107/2064/2107 2106/2063/2106 +f 2107/2064/2107 2118/2074/2118 2119/2075/2119 +f 2119/2075/2119 2108/2065/2108 2107/2064/2107 +f 2109/2002/2109 2120/1981/2120 2121/2076/2121 +f 2121/2076/2121 2110/2066/2110 2109/2002/2109 +f 2110/2066/2110 2121/2076/2121 2122/2077/2122 +f 2122/2077/2122 2111/2067/2111 2110/2066/2110 +f 2111/2067/2111 2122/2077/2122 2123/2078/2123 +f 2123/2078/2123 2112/2068/2112 2111/2067/2111 +f 2112/2068/2112 2123/2078/2123 2124/2079/2124 +f 2124/2079/2124 2113/2069/2113 2112/2068/2112 +f 2113/2069/2113 2124/2079/2124 2125/2080/2125 +f 2125/2080/2125 2114/2070/2114 2113/2069/2113 +f 2114/2070/2114 2125/2080/2125 2126/2081/2126 +f 2126/2081/2126 2115/2071/2115 2114/2070/2114 +f 2115/2071/2115 2126/2081/2126 2127/2082/2127 +f 2127/2082/2127 2116/2072/2116 2115/2071/2115 +f 2116/2072/2116 2127/2082/2127 2128/2083/2128 +f 2128/2083/2128 2117/2073/2117 2116/2072/2116 +f 2117/2073/2117 2128/2083/2128 2129/2084/2129 +f 2129/2084/2129 2118/2074/2118 2117/2073/2117 +f 2118/2074/2118 2129/2084/2129 2130/2085/2130 +f 2130/2085/2130 2119/2075/2119 2118/2074/2118 +f 2120/1981/2120 2131/1980/2131 2132/2086/2132 +f 2132/2086/2132 2121/2076/2121 2120/1981/2120 +f 2121/2076/2121 2132/2086/2132 2133/2087/2133 +f 2133/2087/2133 2122/2077/2122 2121/2076/2121 +f 2122/2077/2122 2133/2087/2133 2134/2088/2134 +f 2134/2088/2134 2123/2078/2123 2122/2077/2122 +f 2123/2078/2123 2134/2088/2134 2135/2089/2135 +f 2135/2089/2135 2124/2079/2124 2123/2078/2123 +f 2124/2079/2124 2135/2089/2135 2136/2090/2136 +f 2136/2090/2136 2125/2080/2125 2124/2079/2124 +f 2125/2080/2125 2136/2090/2136 2137/2091/2137 +f 2137/2091/2137 2126/2081/2126 2125/2080/2125 +f 2126/2081/2126 2137/2091/2137 2138/2092/2138 +f 2138/2092/2138 2127/2082/2127 2126/2081/2126 +f 2127/2082/2127 2138/2092/2138 2139/2093/2139 +f 2139/2093/2139 2128/2083/2128 2127/2082/2127 +f 2128/2083/2128 2139/2093/2139 2140/2094/2140 +f 2140/2094/2140 2129/2084/2129 2128/2083/2128 +f 2129/2084/2129 2140/2094/2140 2141/2095/2141 +f 2141/2095/2141 2130/2085/2130 2129/2084/2129 +f 2131/1980/2131 2142/2096/2142 2143/2097/2143 +f 2143/2097/2143 2132/2086/2132 2131/1980/2131 +f 2132/2086/2132 2143/2097/2143 2144/2098/2144 +f 2144/2098/2144 2133/2087/2133 2132/2086/2132 +f 2133/2087/2133 2144/2098/2144 2145/2099/2145 +f 2145/2099/2145 2134/2088/2134 2133/2087/2133 +f 2134/2088/2134 2145/2099/2145 2146/2100/2146 +f 2146/2100/2146 2135/2089/2135 2134/2088/2134 +f 2135/2089/2135 2146/2100/2146 2147/2101/2147 +f 2147/2101/2147 2136/2090/2136 2135/2089/2135 +f 2136/2090/2136 2147/2101/2147 2148/2102/2148 +f 2148/2102/2148 2137/2091/2137 2136/2090/2136 +f 2137/2091/2137 2148/2102/2148 2149/2103/2149 +f 2149/2103/2149 2138/2092/2138 2137/2091/2137 +f 2138/2092/2138 2149/2103/2149 2150/2104/2150 +f 2150/2104/2150 2139/2093/2139 2138/2092/2138 +f 2139/2093/2139 2150/2104/2150 2151/2105/2151 +f 2151/2105/2151 2140/2094/2140 2139/2093/2139 +f 2140/2094/2140 2151/2105/2151 2152/2106/2152 +f 2152/2106/2152 2141/2095/2141 2140/2094/2140 +f 2142/2096/2142 2153/2107/2153 2154/2108/2154 +f 2154/2108/2154 2143/2097/2143 2142/2096/2142 +f 2143/2097/2143 2154/2108/2154 2155/2109/2155 +f 2155/2109/2155 2144/2098/2144 2143/2097/2143 +f 2144/2098/2144 2155/2109/2155 2156/2110/2156 +f 2156/2110/2156 2145/2099/2145 2144/2098/2144 +f 2145/2099/2145 2156/2110/2156 2157/2111/2157 +f 2157/2111/2157 2146/2100/2146 2145/2099/2145 +f 2146/2100/2146 2157/2111/2157 2158/2112/2158 +f 2158/2112/2158 2147/2101/2147 2146/2100/2146 +f 2147/2101/2147 2158/2112/2158 2159/2113/2159 +f 2159/2113/2159 2148/2102/2148 2147/2101/2147 +f 2148/2102/2148 2159/2113/2159 2160/2114/2160 +f 2160/2114/2160 2149/2103/2149 2148/2102/2148 +f 2149/2103/2149 2160/2114/2160 2161/2115/2161 +f 2161/2115/2161 2150/2104/2150 2149/2103/2149 +f 2150/2104/2150 2161/2115/2161 2162/2116/2162 +f 2162/2116/2162 2151/2105/2151 2150/2104/2150 +f 2151/2105/2151 2162/2116/2162 2163/2117/2163 +f 2163/2117/2163 2152/2106/2152 2151/2105/2151 +f 2153/2107/2153 2164/2118/2164 2165/2119/2165 +f 2165/2119/2165 2154/2108/2154 2153/2107/2153 +f 2154/2108/2154 2165/2119/2165 2166/2120/2166 +f 2166/2120/2166 2155/2109/2155 2154/2108/2154 +f 2155/2109/2155 2166/2120/2166 2167/2121/2167 +f 2167/2121/2167 2156/2110/2156 2155/2109/2155 +f 2156/2110/2156 2167/2121/2167 2168/2122/2168 +f 2168/2122/2168 2157/2111/2157 2156/2110/2156 +f 2157/2111/2157 2168/2122/2168 2169/2123/2169 +f 2169/2123/2169 2158/2112/2158 2157/2111/2157 +f 2158/2112/2158 2169/2123/2169 2170/2124/2170 +f 2170/2124/2170 2159/2113/2159 2158/2112/2158 +f 2159/2113/2159 2170/2124/2170 2171/2125/2171 +f 2171/2125/2171 2160/2114/2160 2159/2113/2159 +f 2160/2114/2160 2171/2125/2171 2172/2126/2172 +f 2172/2126/2172 2161/2115/2161 2160/2114/2160 +f 2161/2115/2161 2172/2126/2172 2173/2127/2173 +f 2173/2127/2173 2162/2116/2162 2161/2115/2161 +f 2162/2116/2162 2173/2127/2173 2174/2128/2174 +f 2174/2128/2174 2163/2117/2163 2162/2116/2162 +f 2164/2118/2164 2175/2129/2175 2176/2130/2176 +f 2176/2130/2176 2165/2119/2165 2164/2118/2164 +f 2165/2119/2165 2176/2130/2176 2177/2131/2177 +f 2177/2131/2177 2166/2120/2166 2165/2119/2165 +f 2166/2120/2166 2177/2131/2177 2178/2132/2178 +f 2178/2132/2178 2167/2121/2167 2166/2120/2166 +f 2167/2121/2167 2178/2132/2178 2179/2133/2179 +f 2179/2133/2179 2168/2122/2168 2167/2121/2167 +f 2168/2122/2168 2179/2133/2179 2180/2134/2180 +f 2180/2134/2180 2169/2123/2169 2168/2122/2168 +f 2169/2123/2169 2180/2134/2180 2181/2135/2181 +f 2181/2135/2181 2170/2124/2170 2169/2123/2169 +f 2170/2124/2170 2181/2135/2181 2182/2136/2182 +f 2182/2136/2182 2171/2125/2171 2170/2124/2170 +f 2171/2125/2171 2182/2136/2182 2183/2137/2183 +f 2183/2137/2183 2172/2126/2172 2171/2125/2171 +f 2172/2126/2172 2183/2137/2183 2184/2138/2184 +f 2184/2138/2184 2173/2127/2173 2172/2126/2172 +f 2173/2127/2173 2184/2138/2184 2185/2139/2185 +f 2185/2139/2185 2174/2128/2174 2173/2127/2173 +f 2175/2129/2175 2186/2140/2186 2187/2141/2187 +f 2187/2141/2187 2176/2130/2176 2175/2129/2175 +f 2176/2130/2176 2187/2141/2187 2188/2142/2188 +f 2188/2142/2188 2177/2131/2177 2176/2130/2176 +f 2177/2131/2177 2188/2142/2188 2189/2143/2189 +f 2189/2143/2189 2178/2132/2178 2177/2131/2177 +f 2178/2132/2178 2189/2143/2189 2190/2144/2190 +f 2190/2144/2190 2179/2133/2179 2178/2132/2178 +f 2179/2133/2179 2190/2144/2190 2191/2145/2191 +f 2191/2145/2191 2180/2134/2180 2179/2133/2179 +f 2180/2134/2180 2191/2145/2191 2192/2146/2192 +f 2192/2146/2192 2181/2135/2181 2180/2134/2180 +f 2181/2135/2181 2192/2146/2192 2193/2147/2193 +f 2193/2147/2193 2182/2136/2182 2181/2135/2181 +f 2182/2136/2182 2193/2147/2193 2194/2148/2194 +f 2194/2148/2194 2183/2137/2183 2182/2136/2182 +f 2183/2137/2183 2194/2148/2194 2195/2149/2195 +f 2195/2149/2195 2184/2138/2184 2183/2137/2183 +f 2184/2138/2184 2195/2149/2195 2196/2150/2196 +f 2196/2150/2196 2185/2139/2185 2184/2138/2184 +f 2186/2140/2186 2197/2129/2197 2198/2151/2198 +f 2198/2151/2198 2187/2141/2187 2186/2140/2186 +f 2187/2141/2187 2198/2151/2198 2199/2152/2199 +f 2199/2152/2199 2188/2142/2188 2187/2141/2187 +f 2188/2142/2188 2199/2152/2199 2200/2153/2200 +f 2200/2153/2200 2189/2143/2189 2188/2142/2188 +f 2189/2143/2189 2200/2153/2200 2201/2154/2201 +f 2201/2154/2201 2190/2144/2190 2189/2143/2189 +f 2190/2144/2190 2201/2154/2201 2202/2155/2202 +f 2202/2155/2202 2191/2145/2191 2190/2144/2190 +f 2191/2145/2191 2202/2155/2202 2203/2156/2203 +f 2203/2156/2203 2192/2146/2192 2191/2145/2191 +f 2192/2146/2192 2203/2156/2203 2204/2157/2204 +f 2204/2157/2204 2193/2147/2193 2192/2146/2192 +f 2193/2147/2193 2204/2157/2204 2205/2158/2205 +f 2205/2158/2205 2194/2148/2194 2193/2147/2193 +f 2194/2148/2194 2205/2158/2205 2206/2159/2206 +f 2206/2159/2206 2195/2149/2195 2194/2148/2194 +f 2195/2149/2195 2206/2159/2206 2207/2160/2207 +f 2207/2160/2207 2196/2150/2196 2195/2149/2195 +f 2197/2129/2197 2208/2118/2208 2209/2161/2209 +f 2209/2161/2209 2198/2151/2198 2197/2129/2197 +f 2198/2151/2198 2209/2161/2209 2210/2162/2210 +f 2210/2162/2210 2199/2152/2199 2198/2151/2198 +f 2199/2152/2199 2210/2162/2210 2211/2163/2211 +f 2211/2163/2211 2200/2153/2200 2199/2152/2199 +f 2200/2153/2200 2211/2163/2211 2212/2164/2212 +f 2212/2164/2212 2201/2154/2201 2200/2153/2200 +f 2201/2154/2201 2212/2164/2212 2213/2165/2213 +f 2213/2165/2213 2202/2155/2202 2201/2154/2201 +f 2202/2155/2202 2213/2165/2213 2214/2166/2214 +f 2214/2166/2214 2203/2156/2203 2202/2155/2202 +f 2203/2156/2203 2214/2166/2214 2215/2167/2215 +f 2215/2167/2215 2204/2157/2204 2203/2156/2203 +f 2204/2157/2204 2215/2167/2215 2216/2168/2216 +f 2216/2168/2216 2205/2158/2205 2204/2157/2204 +f 2205/2158/2205 2216/2168/2216 2217/2169/2217 +f 2217/2169/2217 2206/2159/2206 2205/2158/2205 +f 2206/2159/2206 2217/2169/2217 2218/2170/2218 +f 2218/2170/2218 2207/2160/2207 2206/2159/2206 +f 2208/2118/2208 2219/2107/2219 2220/2171/2220 +f 2220/2171/2220 2209/2161/2209 2208/2118/2208 +f 2209/2161/2209 2220/2171/2220 2221/2172/2221 +f 2221/2172/2221 2210/2162/2210 2209/2161/2209 +f 2210/2162/2210 2221/2172/2221 2222/2173/2222 +f 2222/2173/2222 2211/2163/2211 2210/2162/2210 +f 2211/2163/2211 2222/2173/2222 2223/2174/2223 +f 2223/2174/2223 2212/2164/2212 2211/2163/2211 +f 2212/2164/2212 2223/2174/2223 2224/2175/2224 +f 2224/2175/2224 2213/2165/2213 2212/2164/2212 +f 2213/2165/2213 2224/2175/2224 2225/2176/2225 +f 2225/2176/2225 2214/2166/2214 2213/2165/2213 +f 2214/2166/2214 2225/2176/2225 2226/2177/2226 +f 2226/2177/2226 2215/2167/2215 2214/2166/2214 +f 2215/2167/2215 2226/2177/2226 2227/2178/2227 +f 2227/2178/2227 2216/2168/2216 2215/2167/2215 +f 2216/2168/2216 2227/2178/2227 2228/2179/2228 +f 2228/2179/2228 2217/2169/2217 2216/2168/2216 +f 2217/2169/2217 2228/2179/2228 2229/2180/2229 +f 2229/2180/2229 2218/2170/2218 2217/2169/2217 +f 2219/2107/2219 2230/2096/2230 2231/2181/2231 +f 2231/2181/2231 2220/2171/2220 2219/2107/2219 +f 2220/2171/2220 2231/2181/2231 2232/2182/2232 +f 2232/2182/2232 2221/2172/2221 2220/2171/2220 +f 2221/2172/2221 2232/2182/2232 2233/2183/2233 +f 2233/2183/2233 2222/2173/2222 2221/2172/2221 +f 2222/2173/2222 2233/2183/2233 2234/2184/2234 +f 2234/2184/2234 2223/2174/2223 2222/2173/2222 +f 2223/2174/2223 2234/2184/2234 2235/2185/2235 +f 2235/2185/2235 2224/2175/2224 2223/2174/2223 +f 2224/2175/2224 2235/2185/2235 2236/2186/2236 +f 2236/2186/2236 2225/2176/2225 2224/2175/2224 +f 2225/2176/2225 2236/2186/2236 2237/2187/2237 +f 2237/2187/2237 2226/2177/2226 2225/2176/2225 +f 2226/2177/2226 2237/2187/2237 2238/2188/2238 +f 2238/2188/2238 2227/2178/2227 2226/2177/2226 +f 2227/2178/2227 2238/2188/2238 2239/2189/2239 +f 2239/2189/2239 2228/2179/2228 2227/2178/2227 +f 2228/2179/2228 2239/2189/2239 2240/2190/2240 +f 2240/2190/2240 2229/2180/2229 2228/2179/2228 +f 2230/2096/2230 2021/1980/2021 2024/1983/2024 +f 2024/1983/2024 2231/2181/2231 2230/2096/2230 +f 2231/2181/2231 2024/1983/2024 2026/1985/2026 +f 2026/1985/2026 2232/2182/2232 2231/2181/2231 +f 2232/2182/2232 2026/1985/2026 2028/1987/2028 +f 2028/1987/2028 2233/2183/2233 2232/2182/2232 +f 2233/2183/2233 2028/1987/2028 2030/1989/2030 +f 2030/1989/2030 2234/2184/2234 2233/2183/2233 +f 2234/2184/2234 2030/1989/2030 2032/1991/2032 +f 2032/1991/2032 2235/2185/2235 2234/2184/2234 +f 2235/2185/2235 2032/1991/2032 2034/1993/2034 +f 2034/1993/2034 2236/2186/2236 2235/2185/2235 +f 2236/2186/2236 2034/1993/2034 2036/1995/2036 +f 2036/1995/2036 2237/2187/2237 2236/2186/2236 +f 2237/2187/2237 2036/1995/2036 2038/1997/2038 +f 2038/1997/2038 2238/2188/2238 2237/2187/2237 +f 2238/2188/2238 2038/1997/2038 2040/1999/2040 +f 2040/1999/2040 2239/2189/2239 2238/2188/2238 +f 2239/2189/2239 2040/1999/2040 2042/2001/2042 +f 2042/2001/2042 2240/2190/2240 2239/2189/2239 +f 2042/2001/2042 2041/2000/2041 2241/2191/2241 +f 2241/2191/2241 2242/2192/2242 2042/2001/2042 +f 2242/2192/2242 2241/2191/2241 2243/2193/2243 +f 2243/2193/2243 2244/2194/2244 2242/2192/2242 +f 2244/2194/2244 2243/2193/2243 2245/2195/2245 +f 2245/2195/2245 2246/2196/2246 2244/2194/2244 +f 2246/2196/2246 2245/2195/2245 2247/2197/2247 +f 2247/2197/2247 2248/2198/2248 2246/2196/2246 +f 2248/2198/2248 2247/2197/2247 2249/2199/2249 +f 2249/2199/2249 2250/2200/2250 2248/2198/2248 +f 2250/2200/2250 2249/2199/2249 2251/2201/2251 +f 2251/2201/2251 2252/2202/2252 2250/2200/2250 +f 2252/2202/2252 2251/2201/2251 2253/2203/2253 +f 2253/2203/2253 2254/2204/2254 2252/2202/2252 +f 2254/2204/2254 2253/2203/2253 2255/2205/2255 +f 2255/2205/2255 2256/2206/2256 2254/2204/2254 +f 2256/2206/2256 2255/2205/2255 2257/2207/2257 +f 2257/2207/2257 2258/2208/2258 2256/2206/2256 +f 2258/2208/2258 2257/2207/2257 2259/2209/2259 +f 2259/2209/2259 2260/2210/2260 2258/2208/2258 +f 2041/2000/2041 2053/2012/2053 2261/2211/2261 +f 2261/2211/2261 2241/2191/2241 2041/2000/2041 +f 2241/2191/2241 2261/2211/2261 2262/2212/2262 +f 2262/2212/2262 2243/2193/2243 2241/2191/2241 +f 2243/2193/2243 2262/2212/2262 2263/2213/2263 +f 2263/2213/2263 2245/2195/2245 2243/2193/2243 +f 2245/2195/2245 2263/2213/2263 2264/2214/2264 +f 2264/2214/2264 2247/2197/2247 2245/2195/2245 +f 2247/2197/2247 2264/2214/2264 2265/2215/2265 +f 2265/2215/2265 2249/2199/2249 2247/2197/2247 +f 2249/2199/2249 2265/2215/2265 2266/2216/2266 +f 2266/2216/2266 2251/2201/2251 2249/2199/2249 +f 2251/2201/2251 2266/2216/2266 2267/2217/2267 +f 2267/2217/2267 2253/2203/2253 2251/2201/2251 +f 2253/2203/2253 2267/2217/2267 2268/2218/2268 +f 2268/2218/2268 2255/2205/2255 2253/2203/2253 +f 2255/2205/2255 2268/2218/2268 2269/2219/2269 +f 2269/2219/2269 2257/2207/2257 2255/2205/2255 +f 2257/2207/2257 2269/2219/2269 2270/2220/2270 +f 2270/2220/2270 2259/2209/2259 2257/2207/2257 +f 2053/2012/2053 2064/2023/2064 2271/2221/2271 +f 2271/2221/2271 2261/2211/2261 2053/2012/2053 +f 2261/2211/2261 2271/2221/2271 2272/2222/2272 +f 2272/2222/2272 2262/2212/2262 2261/2211/2261 +f 2262/2212/2262 2272/2222/2272 2273/2223/2273 +f 2273/2223/2273 2263/2213/2263 2262/2212/2262 +f 2263/2213/2263 2273/2223/2273 2274/2224/2274 +f 2274/2224/2274 2264/2214/2264 2263/2213/2263 +f 2264/2214/2264 2274/2224/2274 2275/2225/2275 +f 2275/2225/2275 2265/2215/2265 2264/2214/2264 +f 2265/2215/2265 2275/2225/2275 2276/2226/2276 +f 2276/2226/2276 2266/2216/2266 2265/2215/2265 +f 2266/2216/2266 2276/2226/2276 2277/2227/2277 +f 2277/2227/2277 2267/2217/2267 2266/2216/2266 +f 2267/2217/2267 2277/2227/2277 2278/2228/2278 +f 2278/2228/2278 2268/2218/2268 2267/2217/2267 +f 2268/2218/2268 2278/2228/2278 2279/2229/2279 +f 2279/2229/2279 2269/2219/2269 2268/2218/2268 +f 2269/2219/2269 2279/2229/2279 2280/2230/2280 +f 2280/2230/2280 2270/2220/2270 2269/2219/2269 +f 2064/2023/2064 2075/2034/2075 2281/2231/2281 +f 2281/2231/2281 2271/2221/2271 2064/2023/2064 +f 2271/2221/2271 2281/2231/2281 2282/2232/2282 +f 2282/2232/2282 2272/2222/2272 2271/2221/2271 +f 2272/2222/2272 2282/2232/2282 2283/2233/2283 +f 2283/2233/2283 2273/2223/2273 2272/2222/2272 +f 2273/2223/2273 2283/2233/2283 2284/2234/2284 +f 2284/2234/2284 2274/2224/2274 2273/2223/2273 +f 2274/2224/2274 2284/2234/2284 2285/2235/2285 +f 2285/2235/2285 2275/2225/2275 2274/2224/2274 +f 2275/2225/2275 2285/2235/2285 2286/2236/2286 +f 2286/2236/2286 2276/2226/2276 2275/2225/2275 +f 2276/2226/2276 2286/2236/2286 2287/2237/2287 +f 2287/2237/2287 2277/2227/2277 2276/2226/2276 +f 2277/2227/2277 2287/2237/2287 2288/2238/2288 +f 2288/2238/2288 2278/2228/2278 2277/2227/2277 +f 2278/2228/2278 2288/2238/2288 2289/2239/2289 +f 2289/2239/2289 2279/2229/2279 2278/2228/2278 +f 2279/2229/2279 2289/2239/2289 2290/2240/2290 +f 2290/2240/2290 2280/2230/2280 2279/2229/2279 +f 2075/2034/2075 2086/2045/2086 2291/2241/2291 +f 2291/2241/2291 2281/2231/2281 2075/2034/2075 +f 2281/2231/2281 2291/2241/2291 2292/2242/2292 +f 2292/2242/2292 2282/2232/2282 2281/2231/2281 +f 2282/2232/2282 2292/2242/2292 2293/2243/2293 +f 2293/2243/2293 2283/2233/2283 2282/2232/2282 +f 2283/2233/2283 2293/2243/2293 2294/2244/2294 +f 2294/2244/2294 2284/2234/2284 2283/2233/2283 +f 2284/2234/2284 2294/2244/2294 2295/2245/2295 +f 2295/2245/2295 2285/2235/2285 2284/2234/2284 +f 2285/2235/2285 2295/2245/2295 2296/2246/2296 +f 2296/2246/2296 2286/2236/2286 2285/2235/2285 +f 2286/2236/2286 2296/2246/2296 2297/2247/2297 +f 2297/2247/2297 2287/2237/2287 2286/2236/2286 +f 2287/2237/2287 2297/2247/2297 2298/2248/2298 +f 2298/2248/2298 2288/2238/2288 2287/2237/2287 +f 2288/2238/2288 2298/2248/2298 2299/2249/2299 +f 2299/2249/2299 2289/2239/2289 2288/2238/2288 +f 2289/2239/2289 2299/2249/2299 2300/2250/2300 +f 2300/2250/2300 2290/2240/2290 2289/2239/2289 +f 2086/2045/2086 2097/2055/2097 2301/2251/2301 +f 2301/2251/2301 2291/2241/2291 2086/2045/2086 +f 2291/2241/2291 2301/2251/2301 2302/2252/2302 +f 2302/2252/2302 2292/2242/2292 2291/2241/2291 +f 2292/2242/2292 2302/2252/2302 2303/2253/2303 +f 2303/2253/2303 2293/2243/2293 2292/2242/2292 +f 2293/2243/2293 2303/2253/2303 2304/2254/2304 +f 2304/2254/2304 2294/2244/2294 2293/2243/2293 +f 2294/2244/2294 2304/2254/2304 2305/2255/2305 +f 2305/2255/2305 2295/2245/2295 2294/2244/2294 +f 2295/2245/2295 2305/2255/2305 2306/2256/2306 +f 2306/2256/2306 2296/2246/2296 2295/2245/2295 +f 2296/2246/2296 2306/2256/2306 2307/2257/2307 +f 2307/2257/2307 2297/2247/2297 2296/2246/2296 +f 2297/2247/2297 2307/2257/2307 2308/2258/2308 +f 2308/2258/2308 2298/2248/2298 2297/2247/2297 +f 2298/2248/2298 2308/2258/2308 2309/2259/2309 +f 2309/2259/2309 2299/2249/2299 2298/2248/2298 +f 2299/2249/2299 2309/2259/2309 2310/2260/2310 +f 2310/2260/2310 2300/2250/2300 2299/2249/2299 +f 2097/2055/2097 2108/2065/2108 2311/2261/2311 +f 2311/2261/2311 2301/2251/2301 2097/2055/2097 +f 2301/2251/2301 2311/2261/2311 2312/2262/2312 +f 2312/2262/2312 2302/2252/2302 2301/2251/2301 +f 2302/2252/2302 2312/2262/2312 2313/2263/2313 +f 2313/2263/2313 2303/2253/2303 2302/2252/2302 +f 2303/2253/2303 2313/2263/2313 2314/2264/2314 +f 2314/2264/2314 2304/2254/2304 2303/2253/2303 +f 2304/2254/2304 2314/2264/2314 2315/2265/2315 +f 2315/2265/2315 2305/2255/2305 2304/2254/2304 +f 2305/2255/2305 2315/2265/2315 2316/2266/2316 +f 2316/2266/2316 2306/2256/2306 2305/2255/2305 +f 2306/2256/2306 2316/2266/2316 2317/2267/2317 +f 2317/2267/2317 2307/2257/2307 2306/2256/2306 +f 2307/2257/2307 2317/2267/2317 2318/2268/2318 +f 2318/2268/2318 2308/2258/2308 2307/2257/2307 +f 2308/2258/2308 2318/2268/2318 2319/2269/2319 +f 2319/2269/2319 2309/2259/2309 2308/2258/2308 +f 2309/2259/2309 2319/2269/2319 2320/2270/2320 +f 2320/2270/2320 2310/2260/2310 2309/2259/2309 +f 2108/2065/2108 2119/2075/2119 2321/2271/2321 +f 2321/2271/2321 2311/2261/2311 2108/2065/2108 +f 2311/2261/2311 2321/2271/2321 2322/2272/2322 +f 2322/2272/2322 2312/2262/2312 2311/2261/2311 +f 2312/2262/2312 2322/2272/2322 2323/2273/2323 +f 2323/2273/2323 2313/2263/2313 2312/2262/2312 +f 2313/2263/2313 2323/2273/2323 2324/2274/2324 +f 2324/2274/2324 2314/2264/2314 2313/2263/2313 +f 2314/2264/2314 2324/2274/2324 2325/2275/2325 +f 2325/2275/2325 2315/2265/2315 2314/2264/2314 +f 2315/2265/2315 2325/2275/2325 2326/2276/2326 +f 2326/2276/2326 2316/2266/2316 2315/2265/2315 +f 2316/2266/2316 2326/2276/2326 2327/2277/2327 +f 2327/2277/2327 2317/2267/2317 2316/2266/2316 +f 2317/2267/2317 2327/2277/2327 2328/2278/2328 +f 2328/2278/2328 2318/2268/2318 2317/2267/2317 +f 2318/2268/2318 2328/2278/2328 2329/2279/2329 +f 2329/2279/2329 2319/2269/2319 2318/2268/2318 +f 2319/2269/2319 2329/2279/2329 2330/2280/2330 +f 2330/2280/2330 2320/2270/2320 2319/2269/2319 +f 2119/2075/2119 2130/2085/2130 2331/2281/2331 +f 2331/2281/2331 2321/2271/2321 2119/2075/2119 +f 2321/2271/2321 2331/2281/2331 2332/2282/2332 +f 2332/2282/2332 2322/2272/2322 2321/2271/2321 +f 2322/2272/2322 2332/2282/2332 2333/2283/2333 +f 2333/2283/2333 2323/2273/2323 2322/2272/2322 +f 2323/2273/2323 2333/2283/2333 2334/2284/2334 +f 2334/2284/2334 2324/2274/2324 2323/2273/2323 +f 2324/2274/2324 2334/2284/2334 2335/2285/2335 +f 2335/2285/2335 2325/2275/2325 2324/2274/2324 +f 2325/2275/2325 2335/2285/2335 2336/2286/2336 +f 2336/2286/2336 2326/2276/2326 2325/2275/2325 +f 2326/2276/2326 2336/2286/2336 2337/2287/2337 +f 2337/2287/2337 2327/2277/2327 2326/2276/2326 +f 2327/2277/2327 2337/2287/2337 2338/2288/2338 +f 2338/2288/2338 2328/2278/2328 2327/2277/2327 +f 2328/2278/2328 2338/2288/2338 2339/2289/2339 +f 2339/2289/2339 2329/2279/2329 2328/2278/2328 +f 2329/2279/2329 2339/2289/2339 2340/2290/2340 +f 2340/2290/2340 2330/2280/2330 2329/2279/2329 +f 2130/2085/2130 2141/2095/2141 2341/2291/2341 +f 2341/2291/2341 2331/2281/2331 2130/2085/2130 +f 2331/2281/2331 2341/2291/2341 2342/2292/2342 +f 2342/2292/2342 2332/2282/2332 2331/2281/2331 +f 2332/2282/2332 2342/2292/2342 2343/2293/2343 +f 2343/2293/2343 2333/2283/2333 2332/2282/2332 +f 2333/2283/2333 2343/2293/2343 2344/2294/2344 +f 2344/2294/2344 2334/2284/2334 2333/2283/2333 +f 2334/2284/2334 2344/2294/2344 2345/2295/2345 +f 2345/2295/2345 2335/2285/2335 2334/2284/2334 +f 2335/2285/2335 2345/2295/2345 2346/2296/2346 +f 2346/2296/2346 2336/2286/2336 2335/2285/2335 +f 2336/2286/2336 2346/2296/2346 2347/2297/2347 +f 2347/2297/2347 2337/2287/2337 2336/2286/2336 +f 2337/2287/2337 2347/2297/2347 2348/2298/2348 +f 2348/2298/2348 2338/2288/2338 2337/2287/2337 +f 2338/2288/2338 2348/2298/2348 2349/2299/2349 +f 2349/2299/2349 2339/2289/2339 2338/2288/2338 +f 2339/2289/2339 2349/2299/2349 2350/2300/2350 +f 2350/2300/2350 2340/2290/2340 2339/2289/2339 +f 2141/2095/2141 2152/2106/2152 2351/2301/2351 +f 2351/2301/2351 2341/2291/2341 2141/2095/2141 +f 2341/2291/2341 2351/2301/2351 2352/2302/2352 +f 2352/2302/2352 2342/2292/2342 2341/2291/2341 +f 2342/2292/2342 2352/2302/2352 2353/2303/2353 +f 2353/2303/2353 2343/2293/2343 2342/2292/2342 +f 2343/2293/2343 2353/2303/2353 2354/2304/2354 +f 2354/2304/2354 2344/2294/2344 2343/2293/2343 +f 2344/2294/2344 2354/2304/2354 2355/2305/2355 +f 2355/2305/2355 2345/2295/2345 2344/2294/2344 +f 2345/2295/2345 2355/2305/2355 2356/2306/2356 +f 2356/2306/2356 2346/2296/2346 2345/2295/2345 +f 2346/2296/2346 2356/2306/2356 2357/2307/2357 +f 2357/2307/2357 2347/2297/2347 2346/2296/2346 +f 2347/2297/2347 2357/2307/2357 2358/2308/2358 +f 2358/2308/2358 2348/2298/2348 2347/2297/2347 +f 2348/2298/2348 2358/2308/2358 2359/2309/2359 +f 2359/2309/2359 2349/2299/2349 2348/2298/2348 +f 2349/2299/2349 2359/2309/2359 2360/2310/2360 +f 2360/2310/2360 2350/2300/2350 2349/2299/2349 +f 2152/2106/2152 2163/2117/2163 2361/2311/2361 +f 2361/2311/2361 2351/2301/2351 2152/2106/2152 +f 2351/2301/2351 2361/2311/2361 2362/2312/2362 +f 2362/2312/2362 2352/2302/2352 2351/2301/2351 +f 2352/2302/2352 2362/2312/2362 2363/2313/2363 +f 2363/2313/2363 2353/2303/2353 2352/2302/2352 +f 2353/2303/2353 2363/2313/2363 2364/2314/2364 +f 2364/2314/2364 2354/2304/2354 2353/2303/2353 +f 2354/2304/2354 2364/2314/2364 2365/2315/2365 +f 2365/2315/2365 2355/2305/2355 2354/2304/2354 +f 2355/2305/2355 2365/2315/2365 2366/2316/2366 +f 2366/2316/2366 2356/2306/2356 2355/2305/2355 +f 2356/2306/2356 2366/2316/2366 2367/2317/2367 +f 2367/2317/2367 2357/2307/2357 2356/2306/2356 +f 2357/2307/2357 2367/2317/2367 2368/2318/2368 +f 2368/2318/2368 2358/2308/2358 2357/2307/2357 +f 2358/2308/2358 2368/2318/2368 2369/2319/2369 +f 2369/2319/2369 2359/2309/2359 2358/2308/2358 +f 2359/2309/2359 2369/2319/2369 2370/2320/2370 +f 2370/2320/2370 2360/2310/2360 2359/2309/2359 +f 2163/2117/2163 2174/2128/2174 2371/2321/2371 +f 2371/2321/2371 2361/2311/2361 2163/2117/2163 +f 2361/2311/2361 2371/2321/2371 2372/2322/2372 +f 2372/2322/2372 2362/2312/2362 2361/2311/2361 +f 2362/2312/2362 2372/2322/2372 2373/2323/2373 +f 2373/2323/2373 2363/2313/2363 2362/2312/2362 +f 2363/2313/2363 2373/2323/2373 2374/2324/2374 +f 2374/2324/2374 2364/2314/2364 2363/2313/2363 +f 2364/2314/2364 2374/2324/2374 2375/2325/2375 +f 2375/2325/2375 2365/2315/2365 2364/2314/2364 +f 2365/2315/2365 2375/2325/2375 2376/2326/2376 +f 2376/2326/2376 2366/2316/2366 2365/2315/2365 +f 2366/2316/2366 2376/2326/2376 2377/2327/2377 +f 2377/2327/2377 2367/2317/2367 2366/2316/2366 +f 2367/2317/2367 2377/2327/2377 2378/2328/2378 +f 2378/2328/2378 2368/2318/2368 2367/2317/2367 +f 2368/2318/2368 2378/2328/2378 2379/2329/2379 +f 2379/2329/2379 2369/2319/2369 2368/2318/2368 +f 2369/2319/2369 2379/2329/2379 2380/2330/2380 +f 2380/2330/2380 2370/2320/2370 2369/2319/2369 +f 2174/2128/2174 2185/2139/2185 2381/2331/2381 +f 2381/2331/2381 2371/2321/2371 2174/2128/2174 +f 2371/2321/2371 2381/2331/2381 2382/2332/2382 +f 2382/2332/2382 2372/2322/2372 2371/2321/2371 +f 2372/2322/2372 2382/2332/2382 2383/2333/2383 +f 2383/2333/2383 2373/2323/2373 2372/2322/2372 +f 2373/2323/2373 2383/2333/2383 2384/2334/2384 +f 2384/2334/2384 2374/2324/2374 2373/2323/2373 +f 2374/2324/2374 2384/2334/2384 2385/2335/2385 +f 2385/2335/2385 2375/2325/2375 2374/2324/2374 +f 2375/2325/2375 2385/2335/2385 2386/2336/2386 +f 2386/2336/2386 2376/2326/2376 2375/2325/2375 +f 2376/2326/2376 2386/2336/2386 2387/2337/2387 +f 2387/2337/2387 2377/2327/2377 2376/2326/2376 +f 2377/2327/2377 2387/2337/2387 2388/2338/2388 +f 2388/2338/2388 2378/2328/2378 2377/2327/2377 +f 2378/2328/2378 2388/2338/2388 2389/2339/2389 +f 2389/2339/2389 2379/2329/2379 2378/2328/2378 +f 2379/2329/2379 2389/2339/2389 2390/2340/2390 +f 2390/2340/2390 2380/2330/2380 2379/2329/2379 +f 2185/2139/2185 2196/2150/2196 2391/2341/2391 +f 2391/2341/2391 2381/2331/2381 2185/2139/2185 +f 2381/2331/2381 2391/2341/2391 2392/2342/2392 +f 2392/2342/2392 2382/2332/2382 2381/2331/2381 +f 2382/2332/2382 2392/2342/2392 2393/2343/2393 +f 2393/2343/2393 2383/2333/2383 2382/2332/2382 +f 2383/2333/2383 2393/2343/2393 2394/2344/2394 +f 2394/2344/2394 2384/2334/2384 2383/2333/2383 +f 2384/2334/2384 2394/2344/2394 2395/2345/2395 +f 2395/2345/2395 2385/2335/2385 2384/2334/2384 +f 2385/2335/2385 2395/2345/2395 2396/2346/2396 +f 2396/2346/2396 2386/2336/2386 2385/2335/2385 +f 2386/2336/2386 2396/2346/2396 2397/2347/2397 +f 2397/2347/2397 2387/2337/2387 2386/2336/2386 +f 2387/2337/2387 2397/2347/2397 2398/2348/2398 +f 2398/2348/2398 2388/2338/2388 2387/2337/2387 +f 2388/2338/2388 2398/2348/2398 2399/2349/2399 +f 2399/2349/2399 2389/2339/2389 2388/2338/2388 +f 2389/2339/2389 2399/2349/2399 2400/2350/2400 +f 2400/2350/2400 2390/2340/2390 2389/2339/2389 +f 2196/2150/2196 2207/2160/2207 2401/2351/2401 +f 2401/2351/2401 2391/2341/2391 2196/2150/2196 +f 2391/2341/2391 2401/2351/2401 2402/2352/2402 +f 2402/2352/2402 2392/2342/2392 2391/2341/2391 +f 2392/2342/2392 2402/2352/2402 2403/2353/2403 +f 2403/2353/2403 2393/2343/2393 2392/2342/2392 +f 2393/2343/2393 2403/2353/2403 2404/2354/2404 +f 2404/2354/2404 2394/2344/2394 2393/2343/2393 +f 2394/2344/2394 2404/2354/2404 2405/2355/2405 +f 2405/2355/2405 2395/2345/2395 2394/2344/2394 +f 2395/2345/2395 2405/2355/2405 2406/2356/2406 +f 2406/2356/2406 2396/2346/2396 2395/2345/2395 +f 2396/2346/2396 2406/2356/2406 2407/2357/2407 +f 2407/2357/2407 2397/2347/2397 2396/2346/2396 +f 2397/2347/2397 2407/2357/2407 2408/2358/2408 +f 2408/2358/2408 2398/2348/2398 2397/2347/2397 +f 2398/2348/2398 2408/2358/2408 2409/2359/2409 +f 2409/2359/2409 2399/2349/2399 2398/2348/2398 +f 2399/2349/2399 2409/2359/2409 2410/2360/2410 +f 2410/2360/2410 2400/2350/2400 2399/2349/2399 +f 2207/2160/2207 2218/2170/2218 2411/2361/2411 +f 2411/2361/2411 2401/2351/2401 2207/2160/2207 +f 2401/2351/2401 2411/2361/2411 2412/2362/2412 +f 2412/2362/2412 2402/2352/2402 2401/2351/2401 +f 2402/2352/2402 2412/2362/2412 2413/2363/2413 +f 2413/2363/2413 2403/2353/2403 2402/2352/2402 +f 2403/2353/2403 2413/2363/2413 2414/2364/2414 +f 2414/2364/2414 2404/2354/2404 2403/2353/2403 +f 2404/2354/2404 2414/2364/2414 2415/2365/2415 +f 2415/2365/2415 2405/2355/2405 2404/2354/2404 +f 2405/2355/2405 2415/2365/2415 2416/2366/2416 +f 2416/2366/2416 2406/2356/2406 2405/2355/2405 +f 2406/2356/2406 2416/2366/2416 2417/2367/2417 +f 2417/2367/2417 2407/2357/2407 2406/2356/2406 +f 2407/2357/2407 2417/2367/2417 2418/2368/2418 +f 2418/2368/2418 2408/2358/2408 2407/2357/2407 +f 2408/2358/2408 2418/2368/2418 2419/2369/2419 +f 2419/2369/2419 2409/2359/2409 2408/2358/2408 +f 2409/2359/2409 2419/2369/2419 2420/2370/2420 +f 2420/2370/2420 2410/2360/2410 2409/2359/2409 +f 2218/2170/2218 2229/2180/2229 2421/2371/2421 +f 2421/2371/2421 2411/2361/2411 2218/2170/2218 +f 2411/2361/2411 2421/2371/2421 2422/2372/2422 +f 2422/2372/2422 2412/2362/2412 2411/2361/2411 +f 2412/2362/2412 2422/2372/2422 2423/2373/2423 +f 2423/2373/2423 2413/2363/2413 2412/2362/2412 +f 2413/2363/2413 2423/2373/2423 2424/2374/2424 +f 2424/2374/2424 2414/2364/2414 2413/2363/2413 +f 2414/2364/2414 2424/2374/2424 2425/2375/2425 +f 2425/2375/2425 2415/2365/2415 2414/2364/2414 +f 2415/2365/2415 2425/2375/2425 2426/2376/2426 +f 2426/2376/2426 2416/2366/2416 2415/2365/2415 +f 2416/2366/2416 2426/2376/2426 2427/2377/2427 +f 2427/2377/2427 2417/2367/2417 2416/2366/2416 +f 2417/2367/2417 2427/2377/2427 2428/2378/2428 +f 2428/2378/2428 2418/2368/2418 2417/2367/2417 +f 2418/2368/2418 2428/2378/2428 2429/2379/2429 +f 2429/2379/2429 2419/2369/2419 2418/2368/2418 +f 2419/2369/2419 2429/2379/2429 2430/2380/2430 +f 2430/2380/2430 2420/2370/2420 2419/2369/2419 +f 2229/2180/2229 2240/2190/2240 2431/2381/2431 +f 2431/2381/2431 2421/2371/2421 2229/2180/2229 +f 2421/2371/2421 2431/2381/2431 2432/2382/2432 +f 2432/2382/2432 2422/2372/2422 2421/2371/2421 +f 2422/2372/2422 2432/2382/2432 2433/2383/2433 +f 2433/2383/2433 2423/2373/2423 2422/2372/2422 +f 2423/2373/2423 2433/2383/2433 2434/2384/2434 +f 2434/2384/2434 2424/2374/2424 2423/2373/2423 +f 2424/2374/2424 2434/2384/2434 2435/2385/2435 +f 2435/2385/2435 2425/2375/2425 2424/2374/2424 +f 2425/2375/2425 2435/2385/2435 2436/2386/2436 +f 2436/2386/2436 2426/2376/2426 2425/2375/2425 +f 2426/2376/2426 2436/2386/2436 2437/2387/2437 +f 2437/2387/2437 2427/2377/2427 2426/2376/2426 +f 2427/2377/2427 2437/2387/2437 2438/2388/2438 +f 2438/2388/2438 2428/2378/2428 2427/2377/2427 +f 2428/2378/2428 2438/2388/2438 2439/2389/2439 +f 2439/2389/2439 2429/2379/2429 2428/2378/2428 +f 2429/2379/2429 2439/2389/2439 2440/2390/2440 +f 2440/2390/2440 2430/2380/2430 2429/2379/2429 +f 2240/2190/2240 2042/2001/2042 2242/2192/2242 +f 2242/2192/2242 2431/2381/2431 2240/2190/2240 +f 2431/2381/2431 2242/2192/2242 2244/2194/2244 +f 2244/2194/2244 2432/2382/2432 2431/2381/2431 +f 2432/2382/2432 2244/2194/2244 2246/2196/2246 +f 2246/2196/2246 2433/2383/2433 2432/2382/2432 +f 2433/2383/2433 2246/2196/2246 2248/2198/2248 +f 2248/2198/2248 2434/2384/2434 2433/2383/2433 +f 2434/2384/2434 2248/2198/2248 2250/2200/2250 +f 2250/2200/2250 2435/2385/2435 2434/2384/2434 +f 2435/2385/2435 2250/2200/2250 2252/2202/2252 +f 2252/2202/2252 2436/2386/2436 2435/2385/2435 +f 2436/2386/2436 2252/2202/2252 2254/2204/2254 +f 2254/2204/2254 2437/2387/2437 2436/2386/2436 +f 2437/2387/2437 2254/2204/2254 2256/2206/2256 +f 2256/2206/2256 2438/2388/2438 2437/2387/2437 +f 2438/2388/2438 2256/2206/2256 2258/2208/2258 +f 2258/2208/2258 2439/2389/2439 2438/2388/2438 +f 2439/2389/2439 2258/2208/2258 2260/2210/2260 +f 2260/2210/2260 2440/2390/2440 2439/2389/2439 +f 2442/2391/2441 2443/2392/2442 2441/1219/2443 +f 2443/2392/2442 2442/2391/2441 2444/2393/2444 +f 2444/2393/2444 2445/2394/2445 2443/2392/2442 +f 2445/2394/2445 2444/2393/2444 2446/2395/2446 +f 2446/2395/2446 2447/2396/2447 2445/2394/2445 +f 2447/2396/2447 2446/2395/2446 2448/2397/2448 +f 2448/2397/2448 2449/2398/2449 2447/2396/2447 +f 2449/2398/2449 2448/2397/2448 2450/2399/2450 +f 2450/2399/2450 2451/2400/2451 2449/2398/2449 +f 2451/2400/2451 2450/2399/2450 2452/2401/2452 +f 2452/2401/2452 2453/2402/2453 2451/2400/2451 +f 2453/2402/2453 2452/2401/2452 2454/2403/2454 +f 2454/2403/2454 2455/2404/2455 2453/2402/2453 +f 2455/2404/2455 2454/2403/2454 2456/2405/2456 +f 2456/2405/2456 2457/2406/2457 2455/2404/2455 +f 2457/2406/2457 2456/2405/2456 2458/2407/2458 +f 2458/2407/2458 2459/2408/2459 2457/2406/2457 +f 2459/2408/2459 2458/2407/2458 2460/2409/2460 +f 2460/2409/2460 2461/2410/2461 2459/2408/2459 +f 2462/2411/2462 2442/2391/2441 2441/1219/2443 +f 2442/2391/2441 2462/2411/2462 2463/2412/2463 +f 2463/2412/2463 2444/2393/2444 2442/2391/2441 +f 2444/2393/2444 2463/2412/2463 2464/2413/2464 +f 2464/2413/2464 2446/2395/2446 2444/2393/2444 +f 2446/2395/2446 2464/2413/2464 2465/2414/2465 +f 2465/2414/2465 2448/2397/2448 2446/2395/2446 +f 2448/2397/2448 2465/2414/2465 2466/2415/2466 +f 2466/2415/2466 2450/2399/2450 2448/2397/2448 +f 2450/2399/2450 2466/2415/2466 2467/2416/2467 +f 2467/2416/2467 2452/2401/2452 2450/2399/2450 +f 2452/2401/2452 2467/2416/2467 2468/2417/2468 +f 2468/2417/2468 2454/2403/2454 2452/2401/2452 +f 2454/2403/2454 2468/2417/2468 2469/2418/2469 +f 2469/2418/2469 2456/2405/2456 2454/2403/2454 +f 2456/2405/2456 2469/2418/2469 2470/2419/2470 +f 2470/2419/2470 2458/2407/2458 2456/2405/2456 +f 2458/2407/2458 2470/2419/2470 2471/2420/2471 +f 2471/2420/2471 2460/2409/2460 2458/2407/2458 +f 2472/2421/2472 2462/2411/2462 2441/1219/2443 +f 2462/2411/2462 2472/2421/2472 2473/2422/2473 +f 2473/2422/2473 2463/2412/2463 2462/2411/2462 +f 2463/2412/2463 2473/2422/2473 2474/2423/2474 +f 2474/2423/2474 2464/2413/2464 2463/2412/2463 +f 2464/2413/2464 2474/2423/2474 2475/2424/2475 +f 2475/2424/2475 2465/2414/2465 2464/2413/2464 +f 2465/2414/2465 2475/2424/2475 2476/2425/2476 +f 2476/2425/2476 2466/2415/2466 2465/2414/2465 +f 2466/2415/2466 2476/2425/2476 2477/2426/2477 +f 2477/2426/2477 2467/2416/2467 2466/2415/2466 +f 2467/2416/2467 2477/2426/2477 2478/2427/2478 +f 2478/2427/2478 2468/2417/2468 2467/2416/2467 +f 2468/2417/2468 2478/2427/2478 2479/2428/2479 +f 2479/2428/2479 2469/2418/2469 2468/2417/2468 +f 2469/2418/2469 2479/2428/2479 2480/2429/2480 +f 2480/2429/2480 2470/2419/2470 2469/2418/2469 +f 2470/2419/2470 2480/2429/2480 2481/2430/2481 +f 2481/2430/2481 2471/2420/2471 2470/2419/2470 +f 2482/2431/2482 2472/2421/2472 2441/1219/2443 +f 2472/2421/2472 2482/2431/2482 2483/2432/2483 +f 2483/2432/2483 2473/2422/2473 2472/2421/2472 +f 2473/2422/2473 2483/2432/2483 2484/2433/2484 +f 2484/2433/2484 2474/2423/2474 2473/2422/2473 +f 2474/2423/2474 2484/2433/2484 2485/2434/2485 +f 2485/2434/2485 2475/2424/2475 2474/2423/2474 +f 2475/2424/2475 2485/2434/2485 2486/2435/2486 +f 2486/2435/2486 2476/2425/2476 2475/2424/2475 +f 2476/2425/2476 2486/2435/2486 2487/2436/2487 +f 2487/2436/2487 2477/2426/2477 2476/2425/2476 +f 2477/2426/2477 2487/2436/2487 2488/2437/2488 +f 2488/2437/2488 2478/2427/2478 2477/2426/2477 +f 2478/2427/2478 2488/2437/2488 2489/2438/2489 +f 2489/2438/2489 2479/2428/2479 2478/2427/2478 +f 2479/2428/2479 2489/2438/2489 2490/2439/2490 +f 2490/2439/2490 2480/2429/2480 2479/2428/2479 +f 2480/2429/2480 2490/2439/2490 2491/2440/2491 +f 2491/2440/2491 2481/2430/2481 2480/2429/2480 +f 2492/2441/2492 2482/2431/2482 2441/1219/2443 +f 2482/2431/2482 2492/2441/2492 2493/2442/2493 +f 2493/2442/2493 2483/2432/2483 2482/2431/2482 +f 2483/2432/2483 2493/2442/2493 2494/2443/2494 +f 2494/2443/2494 2484/2433/2484 2483/2432/2483 +f 2484/2433/2484 2494/2443/2494 2495/2444/2495 +f 2495/2444/2495 2485/2434/2485 2484/2433/2484 +f 2485/2434/2485 2495/2444/2495 2496/2445/2496 +f 2496/2445/2496 2486/2435/2486 2485/2434/2485 +f 2486/2435/2486 2496/2445/2496 2497/2446/2497 +f 2497/2446/2497 2487/2436/2487 2486/2435/2486 +f 2487/2436/2487 2497/2446/2497 2498/2447/2498 +f 2498/2447/2498 2488/2437/2488 2487/2436/2487 +f 2488/2437/2488 2498/2447/2498 2499/2448/2499 +f 2499/2448/2499 2489/2438/2489 2488/2437/2488 +f 2489/2438/2489 2499/2448/2499 2500/2449/2500 +f 2500/2449/2500 2490/2439/2490 2489/2438/2489 +f 2490/2439/2490 2500/2449/2500 2501/2450/2501 +f 2501/2450/2501 2491/2440/2491 2490/2439/2490 +f 2502/2451/2502 2492/2441/2492 2441/1219/2443 +f 2492/2441/2492 2502/2451/2502 2503/2452/2503 +f 2503/2452/2503 2493/2442/2493 2492/2441/2492 +f 2493/2442/2493 2503/2452/2503 2504/2453/2504 +f 2504/2453/2504 2494/2443/2494 2493/2442/2493 +f 2494/2443/2494 2504/2453/2504 2505/2454/2505 +f 2505/2454/2505 2495/2444/2495 2494/2443/2494 +f 2495/2444/2495 2505/2454/2505 2506/2455/2506 +f 2506/2455/2506 2496/2445/2496 2495/2444/2495 +f 2496/2445/2496 2506/2455/2506 2507/2456/2507 +f 2507/2456/2507 2497/2446/2497 2496/2445/2496 +f 2497/2446/2497 2507/2456/2507 2508/2457/2508 +f 2508/2457/2508 2498/2447/2498 2497/2446/2497 +f 2498/2447/2498 2508/2457/2508 2509/2458/2509 +f 2509/2458/2509 2499/2448/2499 2498/2447/2498 +f 2499/2448/2499 2509/2458/2509 2510/2459/2510 +f 2510/2459/2510 2500/2449/2500 2499/2448/2499 +f 2500/2449/2500 2510/2459/2510 2511/2460/2511 +f 2511/2460/2511 2501/2450/2501 2500/2449/2500 +f 2512/2461/2512 2502/2451/2502 2441/1219/2443 +f 2502/2451/2502 2512/2461/2512 2513/2462/2513 +f 2513/2462/2513 2503/2452/2503 2502/2451/2502 +f 2503/2452/2503 2513/2462/2513 2514/2463/2514 +f 2514/2463/2514 2504/2453/2504 2503/2452/2503 +f 2504/2453/2504 2514/2463/2514 2515/2464/2515 +f 2515/2464/2515 2505/2454/2505 2504/2453/2504 +f 2505/2454/2505 2515/2464/2515 2516/2465/2516 +f 2516/2465/2516 2506/2455/2506 2505/2454/2505 +f 2506/2455/2506 2516/2465/2516 2517/2466/2517 +f 2517/2466/2517 2507/2456/2507 2506/2455/2506 +f 2507/2456/2507 2517/2466/2517 2518/2467/2518 +f 2518/2467/2518 2508/2457/2508 2507/2456/2507 +f 2508/2457/2508 2518/2467/2518 2519/2468/2519 +f 2519/2468/2519 2509/2458/2509 2508/2457/2508 +f 2509/2458/2509 2519/2468/2519 2520/2469/2520 +f 2520/2469/2520 2510/2459/2510 2509/2458/2509 +f 2510/2459/2510 2520/2469/2520 2521/2470/2521 +f 2521/2470/2521 2511/2460/2511 2510/2459/2510 +f 2522/2471/2522 2512/2461/2512 2441/1219/2443 +f 2512/2461/2512 2522/2471/2522 2523/2472/2523 +f 2523/2472/2523 2513/2462/2513 2512/2461/2512 +f 2513/2462/2513 2523/2472/2523 2524/2473/2524 +f 2524/2473/2524 2514/2463/2514 2513/2462/2513 +f 2514/2463/2514 2524/2473/2524 2525/2474/2525 +f 2525/2474/2525 2515/2464/2515 2514/2463/2514 +f 2515/2464/2515 2525/2474/2525 2526/2475/2526 +f 2526/2475/2526 2516/2465/2516 2515/2464/2515 +f 2516/2465/2516 2526/2475/2526 2527/2476/2527 +f 2527/2476/2527 2517/2466/2517 2516/2465/2516 +f 2517/2466/2517 2527/2476/2527 2528/2477/2528 +f 2528/2477/2528 2518/2467/2518 2517/2466/2517 +f 2518/2467/2518 2528/2477/2528 2529/2478/2529 +f 2529/2478/2529 2519/2468/2519 2518/2467/2518 +f 2519/2468/2519 2529/2478/2529 2530/2479/2530 +f 2530/2479/2530 2520/2469/2520 2519/2468/2519 +f 2520/2469/2520 2530/2479/2530 2531/2480/2531 +f 2531/2480/2531 2521/2470/2521 2520/2469/2520 +f 2532/2481/2532 2522/2471/2522 2441/1219/2443 +f 2522/2471/2522 2532/2481/2532 2533/2482/2533 +f 2533/2482/2533 2523/2472/2523 2522/2471/2522 +f 2523/2472/2523 2533/2482/2533 2534/2483/2534 +f 2534/2483/2534 2524/2473/2524 2523/2472/2523 +f 2524/2473/2524 2534/2483/2534 2535/2484/2535 +f 2535/2484/2535 2525/2474/2525 2524/2473/2524 +f 2525/2474/2525 2535/2484/2535 2536/2485/2536 +f 2536/2485/2536 2526/2475/2526 2525/2474/2525 +f 2526/2475/2526 2536/2485/2536 2537/2486/2537 +f 2537/2486/2537 2527/2476/2527 2526/2475/2526 +f 2527/2476/2527 2537/2486/2537 2538/2487/2538 +f 2538/2487/2538 2528/2477/2528 2527/2476/2527 +f 2528/2477/2528 2538/2487/2538 2539/2488/2539 +f 2539/2488/2539 2529/2478/2529 2528/2477/2528 +f 2529/2478/2529 2539/2488/2539 2540/2489/2540 +f 2540/2489/2540 2530/2479/2530 2529/2478/2529 +f 2530/2479/2530 2540/2489/2540 2541/2490/2541 +f 2541/2490/2541 2531/2480/2531 2530/2479/2530 +f 2542/2491/2542 2532/2481/2532 2441/1219/2443 +f 2532/2481/2532 2542/2491/2542 2543/2492/2543 +f 2543/2492/2543 2533/2482/2533 2532/2481/2532 +f 2533/2482/2533 2543/2492/2543 2544/2493/2544 +f 2544/2493/2544 2534/2483/2534 2533/2482/2533 +f 2534/2483/2534 2544/2493/2544 2545/2494/2545 +f 2545/2494/2545 2535/2484/2535 2534/2483/2534 +f 2535/2484/2535 2545/2494/2545 2546/2495/2546 +f 2546/2495/2546 2536/2485/2536 2535/2484/2535 +f 2536/2485/2536 2546/2495/2546 2547/2496/2547 +f 2547/2496/2547 2537/2486/2537 2536/2485/2536 +f 2537/2486/2537 2547/2496/2547 2548/2497/2548 +f 2548/2497/2548 2538/2487/2538 2537/2486/2537 +f 2538/2487/2538 2548/2497/2548 2549/2498/2549 +f 2549/2498/2549 2539/2488/2539 2538/2487/2538 +f 2539/2488/2539 2549/2498/2549 2550/2499/2550 +f 2550/2499/2550 2540/2489/2540 2539/2488/2539 +f 2540/2489/2540 2550/2499/2550 2551/2500/2551 +f 2551/2500/2551 2541/2490/2541 2540/2489/2540 +f 2552/2501/2552 2542/2491/2542 2441/1219/2443 +f 2542/2491/2542 2552/2501/2552 2553/2502/2553 +f 2553/2502/2553 2543/2492/2543 2542/2491/2542 +f 2543/2492/2543 2553/2502/2553 2554/2503/2554 +f 2554/2503/2554 2544/2493/2544 2543/2492/2543 +f 2544/2493/2544 2554/2503/2554 2555/2504/2555 +f 2555/2504/2555 2545/2494/2545 2544/2493/2544 +f 2545/2494/2545 2555/2504/2555 2556/2505/2556 +f 2556/2505/2556 2546/2495/2546 2545/2494/2545 +f 2546/2495/2546 2556/2505/2556 2557/2506/2557 +f 2557/2506/2557 2547/2496/2547 2546/2495/2546 +f 2547/2496/2547 2557/2506/2557 2558/2507/2558 +f 2558/2507/2558 2548/2497/2548 2547/2496/2547 +f 2548/2497/2548 2558/2507/2558 2559/2508/2559 +f 2559/2508/2559 2549/2498/2549 2548/2497/2548 +f 2549/2498/2549 2559/2508/2559 2560/2509/2560 +f 2560/2509/2560 2550/2499/2550 2549/2498/2549 +f 2550/2499/2550 2560/2509/2560 2561/2510/2561 +f 2561/2510/2561 2551/2500/2551 2550/2499/2550 +f 2562/2511/2562 2552/2501/2552 2441/1219/2443 +f 2552/2501/2552 2562/2511/2562 2563/2512/2563 +f 2563/2512/2563 2553/2502/2553 2552/2501/2552 +f 2553/2502/2553 2563/2512/2563 2564/2513/2564 +f 2564/2513/2564 2554/2503/2554 2553/2502/2553 +f 2554/2503/2554 2564/2513/2564 2565/2514/2565 +f 2565/2514/2565 2555/2504/2555 2554/2503/2554 +f 2555/2504/2555 2565/2514/2565 2566/2515/2566 +f 2566/2515/2566 2556/2505/2556 2555/2504/2555 +f 2556/2505/2556 2566/2515/2566 2567/2516/2567 +f 2567/2516/2567 2557/2506/2557 2556/2505/2556 +f 2557/2506/2557 2567/2516/2567 2568/2517/2568 +f 2568/2517/2568 2558/2507/2558 2557/2506/2557 +f 2558/2507/2558 2568/2517/2568 2569/2518/2569 +f 2569/2518/2569 2559/2508/2559 2558/2507/2558 +f 2559/2508/2559 2569/2518/2569 2570/2519/2570 +f 2570/2519/2570 2560/2509/2560 2559/2508/2559 +f 2560/2509/2560 2570/2519/2570 2571/2520/2571 +f 2571/2520/2571 2561/2510/2561 2560/2509/2560 +f 2572/2521/2572 2562/2511/2562 2441/1219/2443 +f 2562/2511/2562 2572/2521/2572 2573/2522/2573 +f 2573/2522/2573 2563/2512/2563 2562/2511/2562 +f 2563/2512/2563 2573/2522/2573 2574/2523/2574 +f 2574/2523/2574 2564/2513/2564 2563/2512/2563 +f 2564/2513/2564 2574/2523/2574 2575/2524/2575 +f 2575/2524/2575 2565/2514/2565 2564/2513/2564 +f 2565/2514/2565 2575/2524/2575 2576/2525/2576 +f 2576/2525/2576 2566/2515/2566 2565/2514/2565 +f 2566/2515/2566 2576/2525/2576 2577/2526/2577 +f 2577/2526/2577 2567/2516/2567 2566/2515/2566 +f 2567/2516/2567 2577/2526/2577 2578/2527/2578 +f 2578/2527/2578 2568/2517/2568 2567/2516/2567 +f 2568/2517/2568 2578/2527/2578 2579/2528/2579 +f 2579/2528/2579 2569/2518/2569 2568/2517/2568 +f 2569/2518/2569 2579/2528/2579 2580/2529/2580 +f 2580/2529/2580 2570/2519/2570 2569/2518/2569 +f 2570/2519/2570 2580/2529/2580 2581/2530/2581 +f 2581/2530/2581 2571/2520/2571 2570/2519/2570 +f 2582/2531/2582 2572/2521/2572 2441/1219/2443 +f 2572/2521/2572 2582/2531/2582 2583/2532/2583 +f 2583/2532/2583 2573/2522/2573 2572/2521/2572 +f 2573/2522/2573 2583/2532/2583 2584/2533/2584 +f 2584/2533/2584 2574/2523/2574 2573/2522/2573 +f 2574/2523/2574 2584/2533/2584 2585/2534/2585 +f 2585/2534/2585 2575/2524/2575 2574/2523/2574 +f 2575/2524/2575 2585/2534/2585 2586/2535/2586 +f 2586/2535/2586 2576/2525/2576 2575/2524/2575 +f 2576/2525/2576 2586/2535/2586 2587/2536/2587 +f 2587/2536/2587 2577/2526/2577 2576/2525/2576 +f 2577/2526/2577 2587/2536/2587 2588/2537/2588 +f 2588/2537/2588 2578/2527/2578 2577/2526/2577 +f 2578/2527/2578 2588/2537/2588 2589/2538/2589 +f 2589/2538/2589 2579/2528/2579 2578/2527/2578 +f 2579/2528/2579 2589/2538/2589 2590/2539/2590 +f 2590/2539/2590 2580/2529/2580 2579/2528/2579 +f 2580/2529/2580 2590/2539/2590 2591/2540/2591 +f 2591/2540/2591 2581/2530/2581 2580/2529/2580 +f 2592/2541/2592 2582/2531/2582 2441/1219/2443 +f 2582/2531/2582 2592/2541/2592 2593/2542/2593 +f 2593/2542/2593 2583/2532/2583 2582/2531/2582 +f 2583/2532/2583 2593/2542/2593 2594/2543/2594 +f 2594/2543/2594 2584/2533/2584 2583/2532/2583 +f 2584/2533/2584 2594/2543/2594 2595/2544/2595 +f 2595/2544/2595 2585/2534/2585 2584/2533/2584 +f 2585/2534/2585 2595/2544/2595 2596/2545/2596 +f 2596/2545/2596 2586/2535/2586 2585/2534/2585 +f 2586/2535/2586 2596/2545/2596 2597/2546/2597 +f 2597/2546/2597 2587/2536/2587 2586/2535/2586 +f 2587/2536/2587 2597/2546/2597 2598/2547/2598 +f 2598/2547/2598 2588/2537/2588 2587/2536/2587 +f 2588/2537/2588 2598/2547/2598 2599/2548/2599 +f 2599/2548/2599 2589/2538/2589 2588/2537/2588 +f 2589/2538/2589 2599/2548/2599 2600/2549/2600 +f 2600/2549/2600 2590/2539/2590 2589/2538/2589 +f 2590/2539/2590 2600/2549/2600 2601/2550/2601 +f 2601/2550/2601 2591/2540/2591 2590/2539/2590 +f 2602/2551/2602 2592/2541/2592 2441/1219/2443 +f 2592/2541/2592 2602/2551/2602 2603/2552/2603 +f 2603/2552/2603 2593/2542/2593 2592/2541/2592 +f 2593/2542/2593 2603/2552/2603 2604/2553/2604 +f 2604/2553/2604 2594/2543/2594 2593/2542/2593 +f 2594/2543/2594 2604/2553/2604 2605/2554/2605 +f 2605/2554/2605 2595/2544/2595 2594/2543/2594 +f 2595/2544/2595 2605/2554/2605 2606/2555/2606 +f 2606/2555/2606 2596/2545/2596 2595/2544/2595 +f 2596/2545/2596 2606/2555/2606 2607/2556/2607 +f 2607/2556/2607 2597/2546/2597 2596/2545/2596 +f 2597/2546/2597 2607/2556/2607 2608/2557/2608 +f 2608/2557/2608 2598/2547/2598 2597/2546/2597 +f 2598/2547/2598 2608/2557/2608 2609/2558/2609 +f 2609/2558/2609 2599/2548/2599 2598/2547/2598 +f 2599/2548/2599 2609/2558/2609 2610/2559/2610 +f 2610/2559/2610 2600/2549/2600 2599/2548/2599 +f 2600/2549/2600 2610/2559/2610 2611/2560/2611 +f 2611/2560/2611 2601/2550/2601 2600/2549/2600 +f 2612/2561/2612 2602/2551/2602 2441/1219/2443 +f 2602/2551/2602 2612/2561/2612 2613/2562/2613 +f 2613/2562/2613 2603/2552/2603 2602/2551/2602 +f 2603/2552/2603 2613/2562/2613 2614/2563/2614 +f 2614/2563/2614 2604/2553/2604 2603/2552/2603 +f 2604/2553/2604 2614/2563/2614 2615/2564/2615 +f 2615/2564/2615 2605/2554/2605 2604/2553/2604 +f 2605/2554/2605 2615/2564/2615 2616/2565/2616 +f 2616/2565/2616 2606/2555/2606 2605/2554/2605 +f 2606/2555/2606 2616/2565/2616 2617/2566/2617 +f 2617/2566/2617 2607/2556/2607 2606/2555/2606 +f 2607/2556/2607 2617/2566/2617 2618/2567/2618 +f 2618/2567/2618 2608/2557/2608 2607/2556/2607 +f 2608/2557/2608 2618/2567/2618 2619/2568/2619 +f 2619/2568/2619 2609/2558/2609 2608/2557/2608 +f 2609/2558/2609 2619/2568/2619 2620/2569/2620 +f 2620/2569/2620 2610/2559/2610 2609/2558/2609 +f 2610/2559/2610 2620/2569/2620 2621/2570/2621 +f 2621/2570/2621 2611/2560/2611 2610/2559/2610 +f 2622/2571/2622 2612/2561/2612 2441/1219/2443 +f 2612/2561/2612 2622/2571/2622 2623/2572/2623 +f 2623/2572/2623 2613/2562/2613 2612/2561/2612 +f 2613/2562/2613 2623/2572/2623 2624/2573/2624 +f 2624/2573/2624 2614/2563/2614 2613/2562/2613 +f 2614/2563/2614 2624/2573/2624 2625/2574/2625 +f 2625/2574/2625 2615/2564/2615 2614/2563/2614 +f 2615/2564/2615 2625/2574/2625 2626/2575/2626 +f 2626/2575/2626 2616/2565/2616 2615/2564/2615 +f 2616/2565/2616 2626/2575/2626 2627/2576/2627 +f 2627/2576/2627 2617/2566/2617 2616/2565/2616 +f 2617/2566/2617 2627/2576/2627 2628/2577/2628 +f 2628/2577/2628 2618/2567/2618 2617/2566/2617 +f 2618/2567/2618 2628/2577/2628 2629/2578/2629 +f 2629/2578/2629 2619/2568/2619 2618/2567/2618 +f 2619/2568/2619 2629/2578/2629 2630/2579/2630 +f 2630/2579/2630 2620/2569/2620 2619/2568/2619 +f 2620/2569/2620 2630/2579/2630 2631/2580/2631 +f 2631/2580/2631 2621/2570/2621 2620/2569/2620 +f 2632/2581/2632 2622/2571/2622 2441/1219/2443 +f 2622/2571/2622 2632/2581/2632 2633/2582/2633 +f 2633/2582/2633 2623/2572/2623 2622/2571/2622 +f 2623/2572/2623 2633/2582/2633 2634/2583/2634 +f 2634/2583/2634 2624/2573/2624 2623/2572/2623 +f 2624/2573/2624 2634/2583/2634 2635/2584/2635 +f 2635/2584/2635 2625/2574/2625 2624/2573/2624 +f 2625/2574/2625 2635/2584/2635 2636/2585/2636 +f 2636/2585/2636 2626/2575/2626 2625/2574/2625 +f 2626/2575/2626 2636/2585/2636 2637/2586/2637 +f 2637/2586/2637 2627/2576/2627 2626/2575/2626 +f 2627/2576/2627 2637/2586/2637 2638/2587/2638 +f 2638/2587/2638 2628/2577/2628 2627/2576/2627 +f 2628/2577/2628 2638/2587/2638 2639/2588/2639 +f 2639/2588/2639 2629/2578/2629 2628/2577/2628 +f 2629/2578/2629 2639/2588/2639 2640/2589/2640 +f 2640/2589/2640 2630/2579/2630 2629/2578/2629 +f 2630/2579/2630 2640/2589/2640 2641/2590/2641 +f 2641/2590/2641 2631/2580/2631 2630/2579/2630 +f 2642/2591/2642 2632/2581/2632 2441/1219/2443 +f 2632/2581/2632 2642/2591/2642 2643/2592/2643 +f 2643/2592/2643 2633/2582/2633 2632/2581/2632 +f 2633/2582/2633 2643/2592/2643 2644/2593/2644 +f 2644/2593/2644 2634/2583/2634 2633/2582/2633 +f 2634/2583/2634 2644/2593/2644 2645/2594/2645 +f 2645/2594/2645 2635/2584/2635 2634/2583/2634 +f 2635/2584/2635 2645/2594/2645 2646/2595/2646 +f 2646/2595/2646 2636/2585/2636 2635/2584/2635 +f 2636/2585/2636 2646/2595/2646 2647/2596/2647 +f 2647/2596/2647 2637/2586/2637 2636/2585/2636 +f 2637/2586/2637 2647/2596/2647 2648/2597/2648 +f 2648/2597/2648 2638/2587/2638 2637/2586/2637 +f 2638/2587/2638 2648/2597/2648 2649/2598/2649 +f 2649/2598/2649 2639/2588/2639 2638/2587/2638 +f 2639/2588/2639 2649/2598/2649 2650/2599/2650 +f 2650/2599/2650 2640/2589/2640 2639/2588/2639 +f 2640/2589/2640 2650/2599/2650 2651/2600/2651 +f 2651/2600/2651 2641/2590/2641 2640/2589/2640 +f 2652/2601/2652 2642/2591/2642 2441/1219/2443 +f 2642/2591/2642 2652/2601/2652 2653/2602/2653 +f 2653/2602/2653 2643/2592/2643 2642/2591/2642 +f 2643/2592/2643 2653/2602/2653 2654/2603/2654 +f 2654/2603/2654 2644/2593/2644 2643/2592/2643 +f 2644/2593/2644 2654/2603/2654 2655/2604/2655 +f 2655/2604/2655 2645/2594/2645 2644/2593/2644 +f 2645/2594/2645 2655/2604/2655 2656/2605/2656 +f 2656/2605/2656 2646/2595/2646 2645/2594/2645 +f 2646/2595/2646 2656/2605/2656 2657/2606/2657 +f 2657/2606/2657 2647/2596/2647 2646/2595/2646 +f 2647/2596/2647 2657/2606/2657 2658/2607/2658 +f 2658/2607/2658 2648/2597/2648 2647/2596/2647 +f 2648/2597/2648 2658/2607/2658 2659/2608/2659 +f 2659/2608/2659 2649/2598/2649 2648/2597/2648 +f 2649/2598/2649 2659/2608/2659 2660/2609/2660 +f 2660/2609/2660 2650/2599/2650 2649/2598/2649 +f 2650/2599/2650 2660/2609/2660 2661/2610/2661 +f 2661/2610/2661 2651/2600/2651 2650/2599/2650 +f 2662/2611/2662 2652/2601/2652 2441/1219/2443 +f 2652/2601/2652 2662/2611/2662 2663/2612/2663 +f 2663/2612/2663 2653/2602/2653 2652/2601/2652 +f 2653/2602/2653 2663/2612/2663 2664/2613/2664 +f 2664/2613/2664 2654/2603/2654 2653/2602/2653 +f 2654/2603/2654 2664/2613/2664 2665/2614/2665 +f 2665/2614/2665 2655/2604/2655 2654/2603/2654 +f 2655/2604/2655 2665/2614/2665 2666/2615/2666 +f 2666/2615/2666 2656/2605/2656 2655/2604/2655 +f 2656/2605/2656 2666/2615/2666 2667/2616/2667 +f 2667/2616/2667 2657/2606/2657 2656/2605/2656 +f 2657/2606/2657 2667/2616/2667 2668/2617/2668 +f 2668/2617/2668 2658/2607/2658 2657/2606/2657 +f 2658/2607/2658 2668/2617/2668 2669/2618/2669 +f 2669/2618/2669 2659/2608/2659 2658/2607/2658 +f 2659/2608/2659 2669/2618/2669 2670/2619/2670 +f 2670/2619/2670 2660/2609/2660 2659/2608/2659 +f 2660/2609/2660 2670/2619/2670 2671/2620/2671 +f 2671/2620/2671 2661/2610/2661 2660/2609/2660 +f 2672/2621/2672 2662/2611/2662 2441/1219/2443 +f 2662/2611/2662 2672/2621/2672 2673/2622/2673 +f 2673/2622/2673 2663/2612/2663 2662/2611/2662 +f 2663/2612/2663 2673/2622/2673 2674/2623/2674 +f 2674/2623/2674 2664/2613/2664 2663/2612/2663 +f 2664/2613/2664 2674/2623/2674 2675/2624/2675 +f 2675/2624/2675 2665/2614/2665 2664/2613/2664 +f 2665/2614/2665 2675/2624/2675 2676/2625/2676 +f 2676/2625/2676 2666/2615/2666 2665/2614/2665 +f 2666/2615/2666 2676/2625/2676 2677/2626/2677 +f 2677/2626/2677 2667/2616/2667 2666/2615/2666 +f 2667/2616/2667 2677/2626/2677 2678/2627/2678 +f 2678/2627/2678 2668/2617/2668 2667/2616/2667 +f 2668/2617/2668 2678/2627/2678 2679/2628/2679 +f 2679/2628/2679 2669/2618/2669 2668/2617/2668 +f 2669/2618/2669 2679/2628/2679 2680/2629/2680 +f 2680/2629/2680 2670/2619/2670 2669/2618/2669 +f 2670/2619/2670 2680/2629/2680 2681/2630/2681 +f 2681/2630/2681 2671/2620/2671 2670/2619/2670 +f 2682/2631/2682 2672/2621/2672 2441/1219/2443 +f 2672/2621/2672 2682/2631/2682 2683/2632/2683 +f 2683/2632/2683 2673/2622/2673 2672/2621/2672 +f 2673/2622/2673 2683/2632/2683 2684/2633/2684 +f 2684/2633/2684 2674/2623/2674 2673/2622/2673 +f 2674/2623/2674 2684/2633/2684 2685/2634/2685 +f 2685/2634/2685 2675/2624/2675 2674/2623/2674 +f 2675/2624/2675 2685/2634/2685 2686/2635/2686 +f 2686/2635/2686 2676/2625/2676 2675/2624/2675 +f 2676/2625/2676 2686/2635/2686 2687/2636/2687 +f 2687/2636/2687 2677/2626/2677 2676/2625/2676 +f 2677/2626/2677 2687/2636/2687 2688/2637/2688 +f 2688/2637/2688 2678/2627/2678 2677/2626/2677 +f 2678/2627/2678 2688/2637/2688 2689/2638/2689 +f 2689/2638/2689 2679/2628/2679 2678/2627/2678 +f 2679/2628/2679 2689/2638/2689 2690/2639/2690 +f 2690/2639/2690 2680/2629/2680 2679/2628/2679 +f 2680/2629/2680 2690/2639/2690 2691/2640/2691 +f 2691/2640/2691 2681/2630/2681 2680/2629/2680 +f 2692/2641/2692 2682/2631/2682 2441/1219/2443 +f 2682/2631/2682 2692/2641/2692 2693/2642/2693 +f 2693/2642/2693 2683/2632/2683 2682/2631/2682 +f 2683/2632/2683 2693/2642/2693 2694/2643/2694 +f 2694/2643/2694 2684/2633/2684 2683/2632/2683 +f 2684/2633/2684 2694/2643/2694 2695/2644/2695 +f 2695/2644/2695 2685/2634/2685 2684/2633/2684 +f 2685/2634/2685 2695/2644/2695 2696/2645/2696 +f 2696/2645/2696 2686/2635/2686 2685/2634/2685 +f 2686/2635/2686 2696/2645/2696 2697/2646/2697 +f 2697/2646/2697 2687/2636/2687 2686/2635/2686 +f 2687/2636/2687 2697/2646/2697 2698/2647/2698 +f 2698/2647/2698 2688/2637/2688 2687/2636/2687 +f 2688/2637/2688 2698/2647/2698 2699/2648/2699 +f 2699/2648/2699 2689/2638/2689 2688/2637/2688 +f 2689/2638/2689 2699/2648/2699 2700/2649/2700 +f 2700/2649/2700 2690/2639/2690 2689/2638/2689 +f 2690/2639/2690 2700/2649/2700 2701/2650/2701 +f 2701/2650/2701 2691/2640/2691 2690/2639/2690 +f 2702/2651/2702 2692/2641/2692 2441/1219/2443 +f 2692/2641/2692 2702/2651/2702 2703/2652/2703 +f 2703/2652/2703 2693/2642/2693 2692/2641/2692 +f 2693/2642/2693 2703/2652/2703 2704/2653/2704 +f 2704/2653/2704 2694/2643/2694 2693/2642/2693 +f 2694/2643/2694 2704/2653/2704 2705/2654/2705 +f 2705/2654/2705 2695/2644/2695 2694/2643/2694 +f 2695/2644/2695 2705/2654/2705 2706/2655/2706 +f 2706/2655/2706 2696/2645/2696 2695/2644/2695 +f 2696/2645/2696 2706/2655/2706 2707/2656/2707 +f 2707/2656/2707 2697/2646/2697 2696/2645/2696 +f 2697/2646/2697 2707/2656/2707 2708/2657/2708 +f 2708/2657/2708 2698/2647/2698 2697/2646/2697 +f 2698/2647/2698 2708/2657/2708 2709/2658/2709 +f 2709/2658/2709 2699/2648/2699 2698/2647/2698 +f 2699/2648/2699 2709/2658/2709 2710/2659/2710 +f 2710/2659/2710 2700/2649/2700 2699/2648/2699 +f 2700/2649/2700 2710/2659/2710 2711/2660/2711 +f 2711/2660/2711 2701/2650/2701 2700/2649/2700 +f 2712/2661/2712 2702/2651/2702 2441/1219/2443 +f 2702/2651/2702 2712/2661/2712 2713/2662/2713 +f 2713/2662/2713 2703/2652/2703 2702/2651/2702 +f 2703/2652/2703 2713/2662/2713 2714/2663/2714 +f 2714/2663/2714 2704/2653/2704 2703/2652/2703 +f 2704/2653/2704 2714/2663/2714 2715/2664/2715 +f 2715/2664/2715 2705/2654/2705 2704/2653/2704 +f 2705/2654/2705 2715/2664/2715 2716/2665/2716 +f 2716/2665/2716 2706/2655/2706 2705/2654/2705 +f 2706/2655/2706 2716/2665/2716 2717/2666/2717 +f 2717/2666/2717 2707/2656/2707 2706/2655/2706 +f 2707/2656/2707 2717/2666/2717 2718/2667/2718 +f 2718/2667/2718 2708/2657/2708 2707/2656/2707 +f 2708/2657/2708 2718/2667/2718 2719/2668/2719 +f 2719/2668/2719 2709/2658/2709 2708/2657/2708 +f 2709/2658/2709 2719/2668/2719 2720/2669/2720 +f 2720/2669/2720 2710/2659/2710 2709/2658/2709 +f 2710/2659/2710 2720/2669/2720 2721/2670/2721 +f 2721/2670/2721 2711/2660/2711 2710/2659/2710 +f 2722/2671/2722 2712/2661/2712 2441/1219/2443 +f 2712/2661/2712 2722/2671/2722 2723/2672/2723 +f 2723/2672/2723 2713/2662/2713 2712/2661/2712 +f 2713/2662/2713 2723/2672/2723 2724/2673/2724 +f 2724/2673/2724 2714/2663/2714 2713/2662/2713 +f 2714/2663/2714 2724/2673/2724 2725/2674/2725 +f 2725/2674/2725 2715/2664/2715 2714/2663/2714 +f 2715/2664/2715 2725/2674/2725 2726/2675/2726 +f 2726/2675/2726 2716/2665/2716 2715/2664/2715 +f 2716/2665/2716 2726/2675/2726 2727/2676/2727 +f 2727/2676/2727 2717/2666/2717 2716/2665/2716 +f 2717/2666/2717 2727/2676/2727 2728/2677/2728 +f 2728/2677/2728 2718/2667/2718 2717/2666/2717 +f 2718/2667/2718 2728/2677/2728 2729/2678/2729 +f 2729/2678/2729 2719/2668/2719 2718/2667/2718 +f 2719/2668/2719 2729/2678/2729 2730/2679/2730 +f 2730/2679/2730 2720/2669/2720 2719/2668/2719 +f 2720/2669/2720 2730/2679/2730 2731/2680/2731 +f 2731/2680/2731 2721/2670/2721 2720/2669/2720 +f 2732/2681/2732 2722/2671/2722 2441/1219/2443 +f 2722/2671/2722 2732/2681/2732 2733/2682/2733 +f 2733/2682/2733 2723/2672/2723 2722/2671/2722 +f 2723/2672/2723 2733/2682/2733 2734/2683/2734 +f 2734/2683/2734 2724/2673/2724 2723/2672/2723 +f 2724/2673/2724 2734/2683/2734 2735/2684/2735 +f 2735/2684/2735 2725/2674/2725 2724/2673/2724 +f 2725/2674/2725 2735/2684/2735 2736/2685/2736 +f 2736/2685/2736 2726/2675/2726 2725/2674/2725 +f 2726/2675/2726 2736/2685/2736 2737/2686/2737 +f 2737/2686/2737 2727/2676/2727 2726/2675/2726 +f 2727/2676/2727 2737/2686/2737 2738/2687/2738 +f 2738/2687/2738 2728/2677/2728 2727/2676/2727 +f 2728/2677/2728 2738/2687/2738 2739/2688/2739 +f 2739/2688/2739 2729/2678/2729 2728/2677/2728 +f 2729/2678/2729 2739/2688/2739 2740/2689/2740 +f 2740/2689/2740 2730/2679/2730 2729/2678/2729 +f 2730/2679/2730 2740/2689/2740 2741/2690/2741 +f 2741/2690/2741 2731/2680/2731 2730/2679/2730 +f 2742/2691/2742 2732/2681/2732 2441/1219/2443 +f 2732/2681/2732 2742/2691/2742 2743/2692/2743 +f 2743/2692/2743 2733/2682/2733 2732/2681/2732 +f 2733/2682/2733 2743/2692/2743 2744/2693/2744 +f 2744/2693/2744 2734/2683/2734 2733/2682/2733 +f 2734/2683/2734 2744/2693/2744 2745/2694/2745 +f 2745/2694/2745 2735/2684/2735 2734/2683/2734 +f 2735/2684/2735 2745/2694/2745 2746/2695/2746 +f 2746/2695/2746 2736/2685/2736 2735/2684/2735 +f 2736/2685/2736 2746/2695/2746 2747/2696/2747 +f 2747/2696/2747 2737/2686/2737 2736/2685/2736 +f 2737/2686/2737 2747/2696/2747 2748/2697/2748 +f 2748/2697/2748 2738/2687/2738 2737/2686/2737 +f 2738/2687/2738 2748/2697/2748 2749/2698/2749 +f 2749/2698/2749 2739/2688/2739 2738/2687/2738 +f 2739/2688/2739 2749/2698/2749 2750/2699/2750 +f 2750/2699/2750 2740/2689/2740 2739/2688/2739 +f 2740/2689/2740 2750/2699/2750 2751/2700/2751 +f 2751/2700/2751 2741/2690/2741 2740/2689/2740 +f 2752/2701/2752 2742/2691/2742 2441/1219/2443 +f 2742/2691/2742 2752/2701/2752 2753/2702/2753 +f 2753/2702/2753 2743/2692/2743 2742/2691/2742 +f 2743/2692/2743 2753/2702/2753 2754/2703/2754 +f 2754/2703/2754 2744/2693/2744 2743/2692/2743 +f 2744/2693/2744 2754/2703/2754 2755/2704/2755 +f 2755/2704/2755 2745/2694/2745 2744/2693/2744 +f 2745/2694/2745 2755/2704/2755 2756/2705/2756 +f 2756/2705/2756 2746/2695/2746 2745/2694/2745 +f 2746/2695/2746 2756/2705/2756 2757/2706/2757 +f 2757/2706/2757 2747/2696/2747 2746/2695/2746 +f 2747/2696/2747 2757/2706/2757 2758/2707/2758 +f 2758/2707/2758 2748/2697/2748 2747/2696/2747 +f 2748/2697/2748 2758/2707/2758 2759/2708/2759 +f 2759/2708/2759 2749/2698/2749 2748/2697/2748 +f 2749/2698/2749 2759/2708/2759 2760/2709/2760 +f 2760/2709/2760 2750/2699/2750 2749/2698/2749 +f 2750/2699/2750 2760/2709/2760 2761/2710/2761 +f 2761/2710/2761 2751/2700/2751 2750/2699/2750 +f 2762/2711/2762 2752/2701/2752 2441/1219/2443 +f 2752/2701/2752 2762/2711/2762 2763/2712/2763 +f 2763/2712/2763 2753/2702/2753 2752/2701/2752 +f 2753/2702/2753 2763/2712/2763 2764/2713/2764 +f 2764/2713/2764 2754/2703/2754 2753/2702/2753 +f 2754/2703/2754 2764/2713/2764 2765/2714/2765 +f 2765/2714/2765 2755/2704/2755 2754/2703/2754 +f 2755/2704/2755 2765/2714/2765 2766/2715/2766 +f 2766/2715/2766 2756/2705/2756 2755/2704/2755 +f 2756/2705/2756 2766/2715/2766 2767/2716/2767 +f 2767/2716/2767 2757/2706/2757 2756/2705/2756 +f 2757/2706/2757 2767/2716/2767 2768/2717/2768 +f 2768/2717/2768 2758/2707/2758 2757/2706/2757 +f 2758/2707/2758 2768/2717/2768 2769/2718/2769 +f 2769/2718/2769 2759/2708/2759 2758/2707/2758 +f 2759/2708/2759 2769/2718/2769 2770/2719/2770 +f 2770/2719/2770 2760/2709/2760 2759/2708/2759 +f 2760/2709/2760 2770/2719/2770 2771/2720/2771 +f 2771/2720/2771 2761/2710/2761 2760/2709/2760 +f 2772/2721/2772 2762/2711/2762 2441/1219/2443 +f 2762/2711/2762 2772/2721/2772 2773/2722/2773 +f 2773/2722/2773 2763/2712/2763 2762/2711/2762 +f 2763/2712/2763 2773/2722/2773 2774/2723/2774 +f 2774/2723/2774 2764/2713/2764 2763/2712/2763 +f 2764/2713/2764 2774/2723/2774 2775/2724/2775 +f 2775/2724/2775 2765/2714/2765 2764/2713/2764 +f 2765/2714/2765 2775/2724/2775 2776/2725/2776 +f 2776/2725/2776 2766/2715/2766 2765/2714/2765 +f 2766/2715/2766 2776/2725/2776 2777/2726/2777 +f 2777/2726/2777 2767/2716/2767 2766/2715/2766 +f 2767/2716/2767 2777/2726/2777 2778/2727/2778 +f 2778/2727/2778 2768/2717/2768 2767/2716/2767 +f 2768/2717/2768 2778/2727/2778 2779/2728/2779 +f 2779/2728/2779 2769/2718/2769 2768/2717/2768 +f 2769/2718/2769 2779/2728/2779 2780/2729/2780 +f 2780/2729/2780 2770/2719/2770 2769/2718/2769 +f 2770/2719/2770 2780/2729/2780 2781/2730/2781 +f 2781/2730/2781 2771/2720/2771 2770/2719/2770 +f 2782/2731/2782 2772/2721/2772 2441/1219/2443 +f 2772/2721/2772 2782/2731/2782 2783/2732/2783 +f 2783/2732/2783 2773/2722/2773 2772/2721/2772 +f 2773/2722/2773 2783/2732/2783 2784/2733/2784 +f 2784/2733/2784 2774/2723/2774 2773/2722/2773 +f 2774/2723/2774 2784/2733/2784 2785/2734/2785 +f 2785/2734/2785 2775/2724/2775 2774/2723/2774 +f 2775/2724/2775 2785/2734/2785 2786/2735/2786 +f 2786/2735/2786 2776/2725/2776 2775/2724/2775 +f 2776/2725/2776 2786/2735/2786 2787/2736/2787 +f 2787/2736/2787 2777/2726/2777 2776/2725/2776 +f 2777/2726/2777 2787/2736/2787 2788/2737/2788 +f 2788/2737/2788 2778/2727/2778 2777/2726/2777 +f 2778/2727/2778 2788/2737/2788 2789/2738/2789 +f 2789/2738/2789 2779/2728/2779 2778/2727/2778 +f 2779/2728/2779 2789/2738/2789 2790/2739/2790 +f 2790/2739/2790 2780/2729/2780 2779/2728/2779 +f 2780/2729/2780 2790/2739/2790 2791/2740/2791 +f 2791/2740/2791 2781/2730/2781 2780/2729/2780 +f 2792/2741/2792 2782/2731/2782 2441/1219/2443 +f 2782/2731/2782 2792/2741/2792 2793/2742/2793 +f 2793/2742/2793 2783/2732/2783 2782/2731/2782 +f 2783/2732/2783 2793/2742/2793 2794/2743/2794 +f 2794/2743/2794 2784/2733/2784 2783/2732/2783 +f 2784/2733/2784 2794/2743/2794 2795/2744/2795 +f 2795/2744/2795 2785/2734/2785 2784/2733/2784 +f 2785/2734/2785 2795/2744/2795 2796/2745/2796 +f 2796/2745/2796 2786/2735/2786 2785/2734/2785 +f 2786/2735/2786 2796/2745/2796 2797/2746/2797 +f 2797/2746/2797 2787/2736/2787 2786/2735/2786 +f 2787/2736/2787 2797/2746/2797 2798/2747/2798 +f 2798/2747/2798 2788/2737/2788 2787/2736/2787 +f 2788/2737/2788 2798/2747/2798 2799/2748/2799 +f 2799/2748/2799 2789/2738/2789 2788/2737/2788 +f 2789/2738/2789 2799/2748/2799 2800/2749/2800 +f 2800/2749/2800 2790/2739/2790 2789/2738/2789 +f 2790/2739/2790 2800/2749/2800 2801/2750/2801 +f 2801/2750/2801 2791/2740/2791 2790/2739/2790 +f 2802/2751/2802 2792/2741/2792 2441/1219/2443 +f 2792/2741/2792 2802/2751/2802 2803/2752/2803 +f 2803/2752/2803 2793/2742/2793 2792/2741/2792 +f 2793/2742/2793 2803/2752/2803 2804/2753/2804 +f 2804/2753/2804 2794/2743/2794 2793/2742/2793 +f 2794/2743/2794 2804/2753/2804 2805/2754/2805 +f 2805/2754/2805 2795/2744/2795 2794/2743/2794 +f 2795/2744/2795 2805/2754/2805 2806/2755/2806 +f 2806/2755/2806 2796/2745/2796 2795/2744/2795 +f 2796/2745/2796 2806/2755/2806 2807/2756/2807 +f 2807/2756/2807 2797/2746/2797 2796/2745/2796 +f 2797/2746/2797 2807/2756/2807 2808/2757/2808 +f 2808/2757/2808 2798/2747/2798 2797/2746/2797 +f 2798/2747/2798 2808/2757/2808 2809/2758/2809 +f 2809/2758/2809 2799/2748/2799 2798/2747/2798 +f 2799/2748/2799 2809/2758/2809 2810/2759/2810 +f 2810/2759/2810 2800/2749/2800 2799/2748/2799 +f 2800/2749/2800 2810/2759/2810 2811/2760/2811 +f 2811/2760/2811 2801/2750/2801 2800/2749/2800 +f 2812/2761/2812 2802/2751/2802 2441/1219/2443 +f 2802/2751/2802 2812/2761/2812 2813/2762/2813 +f 2813/2762/2813 2803/2752/2803 2802/2751/2802 +f 2803/2752/2803 2813/2762/2813 2814/2763/2814 +f 2814/2763/2814 2804/2753/2804 2803/2752/2803 +f 2804/2753/2804 2814/2763/2814 2815/2764/2815 +f 2815/2764/2815 2805/2754/2805 2804/2753/2804 +f 2805/2754/2805 2815/2764/2815 2816/2765/2816 +f 2816/2765/2816 2806/2755/2806 2805/2754/2805 +f 2806/2755/2806 2816/2765/2816 2817/2766/2817 +f 2817/2766/2817 2807/2756/2807 2806/2755/2806 +f 2807/2756/2807 2817/2766/2817 2818/2767/2818 +f 2818/2767/2818 2808/2757/2808 2807/2756/2807 +f 2808/2757/2808 2818/2767/2818 2819/2768/2819 +f 2819/2768/2819 2809/2758/2809 2808/2757/2808 +f 2809/2758/2809 2819/2768/2819 2820/2769/2820 +f 2820/2769/2820 2810/2759/2810 2809/2758/2809 +f 2810/2759/2810 2820/2769/2820 2821/2770/2821 +f 2821/2770/2821 2811/2760/2811 2810/2759/2810 +f 2822/2771/2822 2812/2761/2812 2441/1219/2443 +f 2812/2761/2812 2822/2771/2822 2823/2772/2823 +f 2823/2772/2823 2813/2762/2813 2812/2761/2812 +f 2813/2762/2813 2823/2772/2823 2824/2773/2824 +f 2824/2773/2824 2814/2763/2814 2813/2762/2813 +f 2814/2763/2814 2824/2773/2824 2825/2774/2825 +f 2825/2774/2825 2815/2764/2815 2814/2763/2814 +f 2815/2764/2815 2825/2774/2825 2826/2775/2826 +f 2826/2775/2826 2816/2765/2816 2815/2764/2815 +f 2816/2765/2816 2826/2775/2826 2827/2776/2827 +f 2827/2776/2827 2817/2766/2817 2816/2765/2816 +f 2817/2766/2817 2827/2776/2827 2828/2777/2828 +f 2828/2777/2828 2818/2767/2818 2817/2766/2817 +f 2818/2767/2818 2828/2777/2828 2829/2778/2829 +f 2829/2778/2829 2819/2768/2819 2818/2767/2818 +f 2819/2768/2819 2829/2778/2829 2830/2779/2830 +f 2830/2779/2830 2820/2769/2820 2819/2768/2819 +f 2820/2769/2820 2830/2779/2830 2831/2780/2831 +f 2831/2780/2831 2821/2770/2821 2820/2769/2820 +f 2832/2781/2832 2822/2771/2822 2441/1219/2443 +f 2822/2771/2822 2832/2781/2832 2833/2782/2833 +f 2833/2782/2833 2823/2772/2823 2822/2771/2822 +f 2823/2772/2823 2833/2782/2833 2834/2783/2834 +f 2834/2783/2834 2824/2773/2824 2823/2772/2823 +f 2824/2773/2824 2834/2783/2834 2835/2784/2835 +f 2835/2784/2835 2825/2774/2825 2824/2773/2824 +f 2825/2774/2825 2835/2784/2835 2836/2785/2836 +f 2836/2785/2836 2826/2775/2826 2825/2774/2825 +f 2826/2775/2826 2836/2785/2836 2837/2786/2837 +f 2837/2786/2837 2827/2776/2827 2826/2775/2826 +f 2827/2776/2827 2837/2786/2837 2838/2787/2838 +f 2838/2787/2838 2828/2777/2828 2827/2776/2827 +f 2828/2777/2828 2838/2787/2838 2839/2788/2839 +f 2839/2788/2839 2829/2778/2829 2828/2777/2828 +f 2829/2778/2829 2839/2788/2839 2840/2789/2840 +f 2840/2789/2840 2830/2779/2830 2829/2778/2829 +f 2830/2779/2830 2840/2789/2840 2841/2790/2841 +f 2841/2790/2841 2831/2780/2831 2830/2779/2830 +f 2443/2392/2442 2832/2781/2832 2441/1219/2443 +f 2832/2781/2832 2443/2392/2442 2445/2394/2445 +f 2445/2394/2445 2833/2782/2833 2832/2781/2832 +f 2833/2782/2833 2445/2394/2445 2447/2396/2447 +f 2447/2396/2447 2834/2783/2834 2833/2782/2833 +f 2834/2783/2834 2447/2396/2447 2449/2398/2449 +f 2449/2398/2449 2835/2784/2835 2834/2783/2834 +f 2835/2784/2835 2449/2398/2449 2451/2400/2451 +f 2451/2400/2451 2836/2785/2836 2835/2784/2835 +f 2836/2785/2836 2451/2400/2451 2453/2402/2453 +f 2453/2402/2453 2837/2786/2837 2836/2785/2836 +f 2837/2786/2837 2453/2402/2453 2455/2404/2455 +f 2455/2404/2455 2838/2787/2838 2837/2786/2837 +f 2838/2787/2838 2455/2404/2455 2457/2406/2457 +f 2457/2406/2457 2839/2788/2839 2838/2787/2838 +f 2839/2788/2839 2457/2406/2457 2459/2408/2459 +f 2459/2408/2459 2840/2789/2840 2839/2788/2839 +f 2840/2789/2840 2459/2408/2459 2461/2410/2461 +f 2461/2410/2461 2841/2790/2841 2840/2789/2840 +f 2461/2410/2461 2460/2409/2460 2842/2791/2842 +f 2842/2791/2842 2843/2792/2843 2461/2410/2461 +f 2843/2792/2843 2842/2791/2842 2844/2793/2844 +f 2844/2793/2844 2845/2794/2845 2843/2792/2843 +f 2845/2794/2845 2844/2793/2844 2846/2795/2846 +f 2846/2795/2846 2847/2796/2847 2845/2794/2845 +f 2847/2796/2847 2846/2795/2846 2848/2797/2848 +f 2848/2797/2848 2849/2798/2849 2847/2796/2847 +f 2849/2798/2849 2848/2797/2848 2850/2799/2850 +f 2850/2799/2850 2851/2800/2851 2849/2798/2849 +f 2851/2800/2851 2850/2799/2850 2852/2801/2852 +f 2852/2801/2852 2853/2802/2853 2851/2800/2851 +f 2853/2802/2853 2852/2801/2852 2854/2803/2854 +f 2854/2803/2854 2855/2804/2855 2853/2802/2853 +f 2855/2804/2855 2854/2803/2854 2856/2805/2856 +f 2856/2805/2856 2857/2806/2857 2855/2804/2855 +f 2857/2806/2857 2856/2805/2856 2858/2807/2858 +f 2858/2807/2858 2859/2808/2859 2857/2806/2857 +f 2859/2808/2859 2858/2807/2858 2860/2809/2860 +f 2860/2809/2860 2861/2810/2861 2859/2808/2859 +f 2460/2409/2460 2471/2420/2471 2862/2811/2862 +f 2862/2811/2862 2842/2791/2842 2460/2409/2460 +f 2842/2791/2842 2862/2811/2862 2863/2812/2863 +f 2863/2812/2863 2844/2793/2844 2842/2791/2842 +f 2844/2793/2844 2863/2812/2863 2864/2813/2864 +f 2864/2813/2864 2846/2795/2846 2844/2793/2844 +f 2846/2795/2846 2864/2813/2864 2865/2814/2865 +f 2865/2814/2865 2848/2797/2848 2846/2795/2846 +f 2848/2797/2848 2865/2814/2865 2866/2815/2866 +f 2866/2815/2866 2850/2799/2850 2848/2797/2848 +f 2850/2799/2850 2866/2815/2866 2867/2816/2867 +f 2867/2816/2867 2852/2801/2852 2850/2799/2850 +f 2852/2801/2852 2867/2816/2867 2868/2817/2868 +f 2868/2817/2868 2854/2803/2854 2852/2801/2852 +f 2854/2803/2854 2868/2817/2868 2869/2818/2869 +f 2869/2818/2869 2856/2805/2856 2854/2803/2854 +f 2856/2805/2856 2869/2818/2869 2870/2819/2870 +f 2870/2819/2870 2858/2807/2858 2856/2805/2856 +f 2858/2807/2858 2870/2819/2870 2871/2820/2871 +f 2871/2820/2871 2860/2809/2860 2858/2807/2858 +f 2471/2420/2471 2481/2430/2481 2872/2821/2872 +f 2872/2821/2872 2862/2811/2862 2471/2420/2471 +f 2862/2811/2862 2872/2821/2872 2873/2822/2873 +f 2873/2822/2873 2863/2812/2863 2862/2811/2862 +f 2863/2812/2863 2873/2822/2873 2874/2823/2874 +f 2874/2823/2874 2864/2813/2864 2863/2812/2863 +f 2864/2813/2864 2874/2823/2874 2875/2824/2875 +f 2875/2824/2875 2865/2814/2865 2864/2813/2864 +f 2865/2814/2865 2875/2824/2875 2876/2825/2876 +f 2876/2825/2876 2866/2815/2866 2865/2814/2865 +f 2866/2815/2866 2876/2825/2876 2877/2826/2877 +f 2877/2826/2877 2867/2816/2867 2866/2815/2866 +f 2867/2816/2867 2877/2826/2877 2878/2827/2878 +f 2878/2827/2878 2868/2817/2868 2867/2816/2867 +f 2868/2817/2868 2878/2827/2878 2879/2828/2879 +f 2879/2828/2879 2869/2818/2869 2868/2817/2868 +f 2869/2818/2869 2879/2828/2879 2880/2829/2880 +f 2880/2829/2880 2870/2819/2870 2869/2818/2869 +f 2870/2819/2870 2880/2829/2880 2881/2830/2881 +f 2881/2830/2881 2871/2820/2871 2870/2819/2870 +f 2481/2430/2481 2491/2440/2491 2882/2831/2882 +f 2882/2831/2882 2872/2821/2872 2481/2430/2481 +f 2872/2821/2872 2882/2831/2882 2883/2832/2883 +f 2883/2832/2883 2873/2822/2873 2872/2821/2872 +f 2873/2822/2873 2883/2832/2883 2884/2833/2884 +f 2884/2833/2884 2874/2823/2874 2873/2822/2873 +f 2874/2823/2874 2884/2833/2884 2885/2834/2885 +f 2885/2834/2885 2875/2824/2875 2874/2823/2874 +f 2875/2824/2875 2885/2834/2885 2886/2835/2886 +f 2886/2835/2886 2876/2825/2876 2875/2824/2875 +f 2876/2825/2876 2886/2835/2886 2887/2836/2887 +f 2887/2836/2887 2877/2826/2877 2876/2825/2876 +f 2877/2826/2877 2887/2836/2887 2888/2837/2888 +f 2888/2837/2888 2878/2827/2878 2877/2826/2877 +f 2878/2827/2878 2888/2837/2888 2889/2838/2889 +f 2889/2838/2889 2879/2828/2879 2878/2827/2878 +f 2879/2828/2879 2889/2838/2889 2890/2839/2890 +f 2890/2839/2890 2880/2829/2880 2879/2828/2879 +f 2880/2829/2880 2890/2839/2890 2891/2840/2891 +f 2891/2840/2891 2881/2830/2881 2880/2829/2880 +f 2491/2440/2491 2501/2450/2501 2892/2841/2892 +f 2892/2841/2892 2882/2831/2882 2491/2440/2491 +f 2882/2831/2882 2892/2841/2892 2893/2842/2893 +f 2893/2842/2893 2883/2832/2883 2882/2831/2882 +f 2883/2832/2883 2893/2842/2893 2894/2843/2894 +f 2894/2843/2894 2884/2833/2884 2883/2832/2883 +f 2884/2833/2884 2894/2843/2894 2895/2844/2895 +f 2895/2844/2895 2885/2834/2885 2884/2833/2884 +f 2885/2834/2885 2895/2844/2895 2896/2845/2896 +f 2896/2845/2896 2886/2835/2886 2885/2834/2885 +f 2886/2835/2886 2896/2845/2896 2897/2846/2897 +f 2897/2846/2897 2887/2836/2887 2886/2835/2886 +f 2887/2836/2887 2897/2846/2897 2898/2847/2898 +f 2898/2847/2898 2888/2837/2888 2887/2836/2887 +f 2888/2837/2888 2898/2847/2898 2899/2848/2899 +f 2899/2848/2899 2889/2838/2889 2888/2837/2888 +f 2889/2838/2889 2899/2848/2899 2900/2849/2900 +f 2900/2849/2900 2890/2839/2890 2889/2838/2889 +f 2890/2839/2890 2900/2849/2900 2901/2850/2901 +f 2901/2850/2901 2891/2840/2891 2890/2839/2890 +f 2501/2450/2501 2511/2460/2511 2902/2851/2902 +f 2902/2851/2902 2892/2841/2892 2501/2450/2501 +f 2892/2841/2892 2902/2851/2902 2903/2852/2903 +f 2903/2852/2903 2893/2842/2893 2892/2841/2892 +f 2893/2842/2893 2903/2852/2903 2904/2853/2904 +f 2904/2853/2904 2894/2843/2894 2893/2842/2893 +f 2894/2843/2894 2904/2853/2904 2905/2854/2905 +f 2905/2854/2905 2895/2844/2895 2894/2843/2894 +f 2895/2844/2895 2905/2854/2905 2906/2855/2906 +f 2906/2855/2906 2896/2845/2896 2895/2844/2895 +f 2896/2845/2896 2906/2855/2906 2907/2856/2907 +f 2907/2856/2907 2897/2846/2897 2896/2845/2896 +f 2897/2846/2897 2907/2856/2907 2908/2857/2908 +f 2908/2857/2908 2898/2847/2898 2897/2846/2897 +f 2898/2847/2898 2908/2857/2908 2909/2858/2909 +f 2909/2858/2909 2899/2848/2899 2898/2847/2898 +f 2899/2848/2899 2909/2858/2909 2910/2859/2910 +f 2910/2859/2910 2900/2849/2900 2899/2848/2899 +f 2900/2849/2900 2910/2859/2910 2911/2860/2911 +f 2911/2860/2911 2901/2850/2901 2900/2849/2900 +f 2511/2460/2511 2521/2470/2521 2912/2861/2912 +f 2912/2861/2912 2902/2851/2902 2511/2460/2511 +f 2902/2851/2902 2912/2861/2912 2913/2862/2913 +f 2913/2862/2913 2903/2852/2903 2902/2851/2902 +f 2903/2852/2903 2913/2862/2913 2914/2863/2914 +f 2914/2863/2914 2904/2853/2904 2903/2852/2903 +f 2904/2853/2904 2914/2863/2914 2915/2864/2915 +f 2915/2864/2915 2905/2854/2905 2904/2853/2904 +f 2905/2854/2905 2915/2864/2915 2916/2865/2916 +f 2916/2865/2916 2906/2855/2906 2905/2854/2905 +f 2906/2855/2906 2916/2865/2916 2917/2866/2917 +f 2917/2866/2917 2907/2856/2907 2906/2855/2906 +f 2907/2856/2907 2917/2866/2917 2918/2867/2918 +f 2918/2867/2918 2908/2857/2908 2907/2856/2907 +f 2908/2857/2908 2918/2867/2918 2919/2868/2919 +f 2919/2868/2919 2909/2858/2909 2908/2857/2908 +f 2909/2858/2909 2919/2868/2919 2920/2869/2920 +f 2920/2869/2920 2910/2859/2910 2909/2858/2909 +f 2910/2859/2910 2920/2869/2920 2921/2870/2921 +f 2921/2870/2921 2911/2860/2911 2910/2859/2910 +f 2521/2470/2521 2531/2480/2531 2922/2871/2922 +f 2922/2871/2922 2912/2861/2912 2521/2470/2521 +f 2912/2861/2912 2922/2871/2922 2923/2872/2923 +f 2923/2872/2923 2913/2862/2913 2912/2861/2912 +f 2913/2862/2913 2923/2872/2923 2924/2873/2924 +f 2924/2873/2924 2914/2863/2914 2913/2862/2913 +f 2914/2863/2914 2924/2873/2924 2925/2874/2925 +f 2925/2874/2925 2915/2864/2915 2914/2863/2914 +f 2915/2864/2915 2925/2874/2925 2926/2875/2926 +f 2926/2875/2926 2916/2865/2916 2915/2864/2915 +f 2916/2865/2916 2926/2875/2926 2927/2876/2927 +f 2927/2876/2927 2917/2866/2917 2916/2865/2916 +f 2917/2866/2917 2927/2876/2927 2928/2877/2928 +f 2928/2877/2928 2918/2867/2918 2917/2866/2917 +f 2918/2867/2918 2928/2877/2928 2929/2878/2929 +f 2929/2878/2929 2919/2868/2919 2918/2867/2918 +f 2919/2868/2919 2929/2878/2929 2930/2879/2930 +f 2930/2879/2930 2920/2869/2920 2919/2868/2919 +f 2920/2869/2920 2930/2879/2930 2931/2880/2931 +f 2931/2880/2931 2921/2870/2921 2920/2869/2920 +f 2531/2480/2531 2541/2490/2541 2932/2881/2932 +f 2932/2881/2932 2922/2871/2922 2531/2480/2531 +f 2922/2871/2922 2932/2881/2932 2933/2882/2933 +f 2933/2882/2933 2923/2872/2923 2922/2871/2922 +f 2923/2872/2923 2933/2882/2933 2934/2883/2934 +f 2934/2883/2934 2924/2873/2924 2923/2872/2923 +f 2924/2873/2924 2934/2883/2934 2935/2884/2935 +f 2935/2884/2935 2925/2874/2925 2924/2873/2924 +f 2925/2874/2925 2935/2884/2935 2936/2885/2936 +f 2936/2885/2936 2926/2875/2926 2925/2874/2925 +f 2926/2875/2926 2936/2885/2936 2937/2886/2937 +f 2937/2886/2937 2927/2876/2927 2926/2875/2926 +f 2927/2876/2927 2937/2886/2937 2938/2887/2938 +f 2938/2887/2938 2928/2877/2928 2927/2876/2927 +f 2928/2877/2928 2938/2887/2938 2939/2888/2939 +f 2939/2888/2939 2929/2878/2929 2928/2877/2928 +f 2929/2878/2929 2939/2888/2939 2940/2889/2940 +f 2940/2889/2940 2930/2879/2930 2929/2878/2929 +f 2930/2879/2930 2940/2889/2940 2941/2890/2941 +f 2941/2890/2941 2931/2880/2931 2930/2879/2930 +f 2541/2490/2541 2551/2500/2551 2942/2891/2942 +f 2942/2891/2942 2932/2881/2932 2541/2490/2541 +f 2932/2881/2932 2942/2891/2942 2943/2892/2943 +f 2943/2892/2943 2933/2882/2933 2932/2881/2932 +f 2933/2882/2933 2943/2892/2943 2944/2893/2944 +f 2944/2893/2944 2934/2883/2934 2933/2882/2933 +f 2934/2883/2934 2944/2893/2944 2945/2894/2945 +f 2945/2894/2945 2935/2884/2935 2934/2883/2934 +f 2935/2884/2935 2945/2894/2945 2946/2895/2946 +f 2946/2895/2946 2936/2885/2936 2935/2884/2935 +f 2936/2885/2936 2946/2895/2946 2947/2896/2947 +f 2947/2896/2947 2937/2886/2937 2936/2885/2936 +f 2937/2886/2937 2947/2896/2947 2948/2897/2948 +f 2948/2897/2948 2938/2887/2938 2937/2886/2937 +f 2938/2887/2938 2948/2897/2948 2949/2898/2949 +f 2949/2898/2949 2939/2888/2939 2938/2887/2938 +f 2939/2888/2939 2949/2898/2949 2950/2899/2950 +f 2950/2899/2950 2940/2889/2940 2939/2888/2939 +f 2940/2889/2940 2950/2899/2950 2951/2900/2951 +f 2951/2900/2951 2941/2890/2941 2940/2889/2940 +f 2551/2500/2551 2561/2510/2561 2952/2901/2952 +f 2952/2901/2952 2942/2891/2942 2551/2500/2551 +f 2942/2891/2942 2952/2901/2952 2953/2902/2953 +f 2953/2902/2953 2943/2892/2943 2942/2891/2942 +f 2943/2892/2943 2953/2902/2953 2954/2903/2954 +f 2954/2903/2954 2944/2893/2944 2943/2892/2943 +f 2944/2893/2944 2954/2903/2954 2955/2904/2955 +f 2955/2904/2955 2945/2894/2945 2944/2893/2944 +f 2945/2894/2945 2955/2904/2955 2956/2905/2956 +f 2956/2905/2956 2946/2895/2946 2945/2894/2945 +f 2946/2895/2946 2956/2905/2956 2957/2906/2957 +f 2957/2906/2957 2947/2896/2947 2946/2895/2946 +f 2947/2896/2947 2957/2906/2957 2958/2907/2958 +f 2958/2907/2958 2948/2897/2948 2947/2896/2947 +f 2948/2897/2948 2958/2907/2958 2959/2908/2959 +f 2959/2908/2959 2949/2898/2949 2948/2897/2948 +f 2949/2898/2949 2959/2908/2959 2960/2909/2960 +f 2960/2909/2960 2950/2899/2950 2949/2898/2949 +f 2950/2899/2950 2960/2909/2960 2961/2910/2961 +f 2961/2910/2961 2951/2900/2951 2950/2899/2950 +f 2561/2510/2561 2571/2520/2571 2962/2911/2962 +f 2962/2911/2962 2952/2901/2952 2561/2510/2561 +f 2952/2901/2952 2962/2911/2962 2963/2912/2963 +f 2963/2912/2963 2953/2902/2953 2952/2901/2952 +f 2953/2902/2953 2963/2912/2963 2964/2913/2964 +f 2964/2913/2964 2954/2903/2954 2953/2902/2953 +f 2954/2903/2954 2964/2913/2964 2965/2914/2965 +f 2965/2914/2965 2955/2904/2955 2954/2903/2954 +f 2955/2904/2955 2965/2914/2965 2966/2915/2966 +f 2966/2915/2966 2956/2905/2956 2955/2904/2955 +f 2956/2905/2956 2966/2915/2966 2967/2916/2967 +f 2967/2916/2967 2957/2906/2957 2956/2905/2956 +f 2957/2906/2957 2967/2916/2967 2968/2917/2968 +f 2968/2917/2968 2958/2907/2958 2957/2906/2957 +f 2958/2907/2958 2968/2917/2968 2969/2918/2969 +f 2969/2918/2969 2959/2908/2959 2958/2907/2958 +f 2959/2908/2959 2969/2918/2969 2970/2919/2970 +f 2970/2919/2970 2960/2909/2960 2959/2908/2959 +f 2960/2909/2960 2970/2919/2970 2971/2920/2971 +f 2971/2920/2971 2961/2910/2961 2960/2909/2960 +f 2571/2520/2571 2581/2530/2581 2972/2921/2972 +f 2972/2921/2972 2962/2911/2962 2571/2520/2571 +f 2962/2911/2962 2972/2921/2972 2973/2922/2973 +f 2973/2922/2973 2963/2912/2963 2962/2911/2962 +f 2963/2912/2963 2973/2922/2973 2974/2923/2974 +f 2974/2923/2974 2964/2913/2964 2963/2912/2963 +f 2964/2913/2964 2974/2923/2974 2975/2924/2975 +f 2975/2924/2975 2965/2914/2965 2964/2913/2964 +f 2965/2914/2965 2975/2924/2975 2976/2925/2976 +f 2976/2925/2976 2966/2915/2966 2965/2914/2965 +f 2966/2915/2966 2976/2925/2976 2977/2926/2977 +f 2977/2926/2977 2967/2916/2967 2966/2915/2966 +f 2967/2916/2967 2977/2926/2977 2978/2927/2978 +f 2978/2927/2978 2968/2917/2968 2967/2916/2967 +f 2968/2917/2968 2978/2927/2978 2979/2928/2979 +f 2979/2928/2979 2969/2918/2969 2968/2917/2968 +f 2969/2918/2969 2979/2928/2979 2980/2929/2980 +f 2980/2929/2980 2970/2919/2970 2969/2918/2969 +f 2970/2919/2970 2980/2929/2980 2981/2930/2981 +f 2981/2930/2981 2971/2920/2971 2970/2919/2970 +f 2581/2530/2581 2591/2540/2591 2982/2931/2982 +f 2982/2931/2982 2972/2921/2972 2581/2530/2581 +f 2972/2921/2972 2982/2931/2982 2983/2932/2983 +f 2983/2932/2983 2973/2922/2973 2972/2921/2972 +f 2973/2922/2973 2983/2932/2983 2984/2933/2984 +f 2984/2933/2984 2974/2923/2974 2973/2922/2973 +f 2974/2923/2974 2984/2933/2984 2985/2934/2985 +f 2985/2934/2985 2975/2924/2975 2974/2923/2974 +f 2975/2924/2975 2985/2934/2985 2986/2935/2986 +f 2986/2935/2986 2976/2925/2976 2975/2924/2975 +f 2976/2925/2976 2986/2935/2986 2987/2936/2987 +f 2987/2936/2987 2977/2926/2977 2976/2925/2976 +f 2977/2926/2977 2987/2936/2987 2988/2937/2988 +f 2988/2937/2988 2978/2927/2978 2977/2926/2977 +f 2978/2927/2978 2988/2937/2988 2989/2938/2989 +f 2989/2938/2989 2979/2928/2979 2978/2927/2978 +f 2979/2928/2979 2989/2938/2989 2990/2939/2990 +f 2990/2939/2990 2980/2929/2980 2979/2928/2979 +f 2980/2929/2980 2990/2939/2990 2991/2940/2991 +f 2991/2940/2991 2981/2930/2981 2980/2929/2980 +f 2591/2540/2591 2601/2550/2601 2992/2941/2992 +f 2992/2941/2992 2982/2931/2982 2591/2540/2591 +f 2982/2931/2982 2992/2941/2992 2993/2942/2993 +f 2993/2942/2993 2983/2932/2983 2982/2931/2982 +f 2983/2932/2983 2993/2942/2993 2994/2943/2994 +f 2994/2943/2994 2984/2933/2984 2983/2932/2983 +f 2984/2933/2984 2994/2943/2994 2995/2944/2995 +f 2995/2944/2995 2985/2934/2985 2984/2933/2984 +f 2985/2934/2985 2995/2944/2995 2996/2945/2996 +f 2996/2945/2996 2986/2935/2986 2985/2934/2985 +f 2986/2935/2986 2996/2945/2996 2997/2946/2997 +f 2997/2946/2997 2987/2936/2987 2986/2935/2986 +f 2987/2936/2987 2997/2946/2997 2998/2947/2998 +f 2998/2947/2998 2988/2937/2988 2987/2936/2987 +f 2988/2937/2988 2998/2947/2998 2999/2948/2999 +f 2999/2948/2999 2989/2938/2989 2988/2937/2988 +f 2989/2938/2989 2999/2948/2999 3000/2949/3000 +f 3000/2949/3000 2990/2939/2990 2989/2938/2989 +f 2990/2939/2990 3000/2949/3000 3001/2950/3001 +f 3001/2950/3001 2991/2940/2991 2990/2939/2990 +f 2601/2550/2601 2611/2560/2611 3002/2951/3002 +f 3002/2951/3002 2992/2941/2992 2601/2550/2601 +f 2992/2941/2992 3002/2951/3002 3003/2952/3003 +f 3003/2952/3003 2993/2942/2993 2992/2941/2992 +f 2993/2942/2993 3003/2952/3003 3004/2953/3004 +f 3004/2953/3004 2994/2943/2994 2993/2942/2993 +f 2994/2943/2994 3004/2953/3004 3005/2954/3005 +f 3005/2954/3005 2995/2944/2995 2994/2943/2994 +f 2995/2944/2995 3005/2954/3005 3006/2955/3006 +f 3006/2955/3006 2996/2945/2996 2995/2944/2995 +f 2996/2945/2996 3006/2955/3006 3007/2956/3007 +f 3007/2956/3007 2997/2946/2997 2996/2945/2996 +f 2997/2946/2997 3007/2956/3007 3008/2957/3008 +f 3008/2957/3008 2998/2947/2998 2997/2946/2997 +f 2998/2947/2998 3008/2957/3008 3009/2958/3009 +f 3009/2958/3009 2999/2948/2999 2998/2947/2998 +f 2999/2948/2999 3009/2958/3009 3010/2959/3010 +f 3010/2959/3010 3000/2949/3000 2999/2948/2999 +f 3000/2949/3000 3010/2959/3010 3011/2960/3011 +f 3011/2960/3011 3001/2950/3001 3000/2949/3000 +f 2611/2560/2611 2621/2570/2621 3012/2961/3012 +f 3012/2961/3012 3002/2951/3002 2611/2560/2611 +f 3002/2951/3002 3012/2961/3012 3013/2962/3013 +f 3013/2962/3013 3003/2952/3003 3002/2951/3002 +f 3003/2952/3003 3013/2962/3013 3014/2963/3014 +f 3014/2963/3014 3004/2953/3004 3003/2952/3003 +f 3004/2953/3004 3014/2963/3014 3015/2964/3015 +f 3015/2964/3015 3005/2954/3005 3004/2953/3004 +f 3005/2954/3005 3015/2964/3015 3016/2965/3016 +f 3016/2965/3016 3006/2955/3006 3005/2954/3005 +f 3006/2955/3006 3016/2965/3016 3017/2966/3017 +f 3017/2966/3017 3007/2956/3007 3006/2955/3006 +f 3007/2956/3007 3017/2966/3017 3018/2967/3018 +f 3018/2967/3018 3008/2957/3008 3007/2956/3007 +f 3008/2957/3008 3018/2967/3018 3019/2968/3019 +f 3019/2968/3019 3009/2958/3009 3008/2957/3008 +f 3009/2958/3009 3019/2968/3019 3020/2969/3020 +f 3020/2969/3020 3010/2959/3010 3009/2958/3009 +f 3010/2959/3010 3020/2969/3020 3021/2970/3021 +f 3021/2970/3021 3011/2960/3011 3010/2959/3010 +f 2621/2570/2621 2631/2580/2631 3022/2971/3022 +f 3022/2971/3022 3012/2961/3012 2621/2570/2621 +f 3012/2961/3012 3022/2971/3022 3023/2972/3023 +f 3023/2972/3023 3013/2962/3013 3012/2961/3012 +f 3013/2962/3013 3023/2972/3023 3024/2973/3024 +f 3024/2973/3024 3014/2963/3014 3013/2962/3013 +f 3014/2963/3014 3024/2973/3024 3025/2974/3025 +f 3025/2974/3025 3015/2964/3015 3014/2963/3014 +f 3015/2964/3015 3025/2974/3025 3026/2975/3026 +f 3026/2975/3026 3016/2965/3016 3015/2964/3015 +f 3016/2965/3016 3026/2975/3026 3027/2976/3027 +f 3027/2976/3027 3017/2966/3017 3016/2965/3016 +f 3017/2966/3017 3027/2976/3027 3028/2977/3028 +f 3028/2977/3028 3018/2967/3018 3017/2966/3017 +f 3018/2967/3018 3028/2977/3028 3029/2978/3029 +f 3029/2978/3029 3019/2968/3019 3018/2967/3018 +f 3019/2968/3019 3029/2978/3029 3030/2979/3030 +f 3030/2979/3030 3020/2969/3020 3019/2968/3019 +f 3020/2969/3020 3030/2979/3030 3031/2980/3031 +f 3031/2980/3031 3021/2970/3021 3020/2969/3020 +f 2631/2580/2631 2641/2590/2641 3032/2981/3032 +f 3032/2981/3032 3022/2971/3022 2631/2580/2631 +f 3022/2971/3022 3032/2981/3032 3033/2982/3033 +f 3033/2982/3033 3023/2972/3023 3022/2971/3022 +f 3023/2972/3023 3033/2982/3033 3034/2983/3034 +f 3034/2983/3034 3024/2973/3024 3023/2972/3023 +f 3024/2973/3024 3034/2983/3034 3035/2984/3035 +f 3035/2984/3035 3025/2974/3025 3024/2973/3024 +f 3025/2974/3025 3035/2984/3035 3036/2985/3036 +f 3036/2985/3036 3026/2975/3026 3025/2974/3025 +f 3026/2975/3026 3036/2985/3036 3037/2986/3037 +f 3037/2986/3037 3027/2976/3027 3026/2975/3026 +f 3027/2976/3027 3037/2986/3037 3038/2987/3038 +f 3038/2987/3038 3028/2977/3028 3027/2976/3027 +f 3028/2977/3028 3038/2987/3038 3039/2988/3039 +f 3039/2988/3039 3029/2978/3029 3028/2977/3028 +f 3029/2978/3029 3039/2988/3039 3040/2989/3040 +f 3040/2989/3040 3030/2979/3030 3029/2978/3029 +f 3030/2979/3030 3040/2989/3040 3041/2990/3041 +f 3041/2990/3041 3031/2980/3031 3030/2979/3030 +f 2641/2590/2641 2651/2600/2651 3042/2991/3042 +f 3042/2991/3042 3032/2981/3032 2641/2590/2641 +f 3032/2981/3032 3042/2991/3042 3043/2992/3043 +f 3043/2992/3043 3033/2982/3033 3032/2981/3032 +f 3033/2982/3033 3043/2992/3043 3044/2993/3044 +f 3044/2993/3044 3034/2983/3034 3033/2982/3033 +f 3034/2983/3034 3044/2993/3044 3045/2994/3045 +f 3045/2994/3045 3035/2984/3035 3034/2983/3034 +f 3035/2984/3035 3045/2994/3045 3046/2995/3046 +f 3046/2995/3046 3036/2985/3036 3035/2984/3035 +f 3036/2985/3036 3046/2995/3046 3047/2996/3047 +f 3047/2996/3047 3037/2986/3037 3036/2985/3036 +f 3037/2986/3037 3047/2996/3047 3048/2997/3048 +f 3048/2997/3048 3038/2987/3038 3037/2986/3037 +f 3038/2987/3038 3048/2997/3048 3049/2998/3049 +f 3049/2998/3049 3039/2988/3039 3038/2987/3038 +f 3039/2988/3039 3049/2998/3049 3050/2999/3050 +f 3050/2999/3050 3040/2989/3040 3039/2988/3039 +f 3040/2989/3040 3050/2999/3050 3051/3000/3051 +f 3051/3000/3051 3041/2990/3041 3040/2989/3040 +f 2651/2600/2651 2661/2610/2661 3052/3001/3052 +f 3052/3001/3052 3042/2991/3042 2651/2600/2651 +f 3042/2991/3042 3052/3001/3052 3053/3002/3053 +f 3053/3002/3053 3043/2992/3043 3042/2991/3042 +f 3043/2992/3043 3053/3002/3053 3054/3003/3054 +f 3054/3003/3054 3044/2993/3044 3043/2992/3043 +f 3044/2993/3044 3054/3003/3054 3055/3004/3055 +f 3055/3004/3055 3045/2994/3045 3044/2993/3044 +f 3045/2994/3045 3055/3004/3055 3056/3005/3056 +f 3056/3005/3056 3046/2995/3046 3045/2994/3045 +f 3046/2995/3046 3056/3005/3056 3057/3006/3057 +f 3057/3006/3057 3047/2996/3047 3046/2995/3046 +f 3047/2996/3047 3057/3006/3057 3058/3007/3058 +f 3058/3007/3058 3048/2997/3048 3047/2996/3047 +f 3048/2997/3048 3058/3007/3058 3059/3008/3059 +f 3059/3008/3059 3049/2998/3049 3048/2997/3048 +f 3049/2998/3049 3059/3008/3059 3060/3009/3060 +f 3060/3009/3060 3050/2999/3050 3049/2998/3049 +f 3050/2999/3050 3060/3009/3060 3061/3010/3061 +f 3061/3010/3061 3051/3000/3051 3050/2999/3050 +f 2661/2610/2661 2671/2620/2671 3062/3011/3062 +f 3062/3011/3062 3052/3001/3052 2661/2610/2661 +f 3052/3001/3052 3062/3011/3062 3063/3012/3063 +f 3063/3012/3063 3053/3002/3053 3052/3001/3052 +f 3053/3002/3053 3063/3012/3063 3064/3013/3064 +f 3064/3013/3064 3054/3003/3054 3053/3002/3053 +f 3054/3003/3054 3064/3013/3064 3065/3014/3065 +f 3065/3014/3065 3055/3004/3055 3054/3003/3054 +f 3055/3004/3055 3065/3014/3065 3066/3015/3066 +f 3066/3015/3066 3056/3005/3056 3055/3004/3055 +f 3056/3005/3056 3066/3015/3066 3067/3016/3067 +f 3067/3016/3067 3057/3006/3057 3056/3005/3056 +f 3057/3006/3057 3067/3016/3067 3068/3017/3068 +f 3068/3017/3068 3058/3007/3058 3057/3006/3057 +f 3058/3007/3058 3068/3017/3068 3069/3018/3069 +f 3069/3018/3069 3059/3008/3059 3058/3007/3058 +f 3059/3008/3059 3069/3018/3069 3070/3019/3070 +f 3070/3019/3070 3060/3009/3060 3059/3008/3059 +f 3060/3009/3060 3070/3019/3070 3071/3020/3071 +f 3071/3020/3071 3061/3010/3061 3060/3009/3060 +f 2671/2620/2671 2681/2630/2681 3072/3021/3072 +f 3072/3021/3072 3062/3011/3062 2671/2620/2671 +f 3062/3011/3062 3072/3021/3072 3073/3022/3073 +f 3073/3022/3073 3063/3012/3063 3062/3011/3062 +f 3063/3012/3063 3073/3022/3073 3074/3023/3074 +f 3074/3023/3074 3064/3013/3064 3063/3012/3063 +f 3064/3013/3064 3074/3023/3074 3075/3024/3075 +f 3075/3024/3075 3065/3014/3065 3064/3013/3064 +f 3065/3014/3065 3075/3024/3075 3076/3025/3076 +f 3076/3025/3076 3066/3015/3066 3065/3014/3065 +f 3066/3015/3066 3076/3025/3076 3077/3026/3077 +f 3077/3026/3077 3067/3016/3067 3066/3015/3066 +f 3067/3016/3067 3077/3026/3077 3078/3027/3078 +f 3078/3027/3078 3068/3017/3068 3067/3016/3067 +f 3068/3017/3068 3078/3027/3078 3079/3028/3079 +f 3079/3028/3079 3069/3018/3069 3068/3017/3068 +f 3069/3018/3069 3079/3028/3079 3080/3029/3080 +f 3080/3029/3080 3070/3019/3070 3069/3018/3069 +f 3070/3019/3070 3080/3029/3080 3081/3030/3081 +f 3081/3030/3081 3071/3020/3071 3070/3019/3070 +f 2681/2630/2681 2691/2640/2691 3082/3031/3082 +f 3082/3031/3082 3072/3021/3072 2681/2630/2681 +f 3072/3021/3072 3082/3031/3082 3083/3032/3083 +f 3083/3032/3083 3073/3022/3073 3072/3021/3072 +f 3073/3022/3073 3083/3032/3083 3084/3033/3084 +f 3084/3033/3084 3074/3023/3074 3073/3022/3073 +f 3074/3023/3074 3084/3033/3084 3085/3034/3085 +f 3085/3034/3085 3075/3024/3075 3074/3023/3074 +f 3075/3024/3075 3085/3034/3085 3086/3035/3086 +f 3086/3035/3086 3076/3025/3076 3075/3024/3075 +f 3076/3025/3076 3086/3035/3086 3087/3036/3087 +f 3087/3036/3087 3077/3026/3077 3076/3025/3076 +f 3077/3026/3077 3087/3036/3087 3088/3037/3088 +f 3088/3037/3088 3078/3027/3078 3077/3026/3077 +f 3078/3027/3078 3088/3037/3088 3089/3038/3089 +f 3089/3038/3089 3079/3028/3079 3078/3027/3078 +f 3079/3028/3079 3089/3038/3089 3090/3039/3090 +f 3090/3039/3090 3080/3029/3080 3079/3028/3079 +f 3080/3029/3080 3090/3039/3090 3091/3040/3091 +f 3091/3040/3091 3081/3030/3081 3080/3029/3080 +f 2691/2640/2691 2701/2650/2701 3092/3041/3092 +f 3092/3041/3092 3082/3031/3082 2691/2640/2691 +f 3082/3031/3082 3092/3041/3092 3093/3042/3093 +f 3093/3042/3093 3083/3032/3083 3082/3031/3082 +f 3083/3032/3083 3093/3042/3093 3094/3043/3094 +f 3094/3043/3094 3084/3033/3084 3083/3032/3083 +f 3084/3033/3084 3094/3043/3094 3095/3044/3095 +f 3095/3044/3095 3085/3034/3085 3084/3033/3084 +f 3085/3034/3085 3095/3044/3095 3096/3045/3096 +f 3096/3045/3096 3086/3035/3086 3085/3034/3085 +f 3086/3035/3086 3096/3045/3096 3097/3046/3097 +f 3097/3046/3097 3087/3036/3087 3086/3035/3086 +f 3087/3036/3087 3097/3046/3097 3098/3047/3098 +f 3098/3047/3098 3088/3037/3088 3087/3036/3087 +f 3088/3037/3088 3098/3047/3098 3099/3048/3099 +f 3099/3048/3099 3089/3038/3089 3088/3037/3088 +f 3089/3038/3089 3099/3048/3099 3100/3049/3100 +f 3100/3049/3100 3090/3039/3090 3089/3038/3089 +f 3090/3039/3090 3100/3049/3100 3101/3050/3101 +f 3101/3050/3101 3091/3040/3091 3090/3039/3090 +f 2701/2650/2701 2711/2660/2711 3102/3051/3102 +f 3102/3051/3102 3092/3041/3092 2701/2650/2701 +f 3092/3041/3092 3102/3051/3102 3103/3052/3103 +f 3103/3052/3103 3093/3042/3093 3092/3041/3092 +f 3093/3042/3093 3103/3052/3103 3104/3053/3104 +f 3104/3053/3104 3094/3043/3094 3093/3042/3093 +f 3094/3043/3094 3104/3053/3104 3105/3054/3105 +f 3105/3054/3105 3095/3044/3095 3094/3043/3094 +f 3095/3044/3095 3105/3054/3105 3106/3055/3106 +f 3106/3055/3106 3096/3045/3096 3095/3044/3095 +f 3096/3045/3096 3106/3055/3106 3107/3056/3107 +f 3107/3056/3107 3097/3046/3097 3096/3045/3096 +f 3097/3046/3097 3107/3056/3107 3108/3057/3108 +f 3108/3057/3108 3098/3047/3098 3097/3046/3097 +f 3098/3047/3098 3108/3057/3108 3109/3058/3109 +f 3109/3058/3109 3099/3048/3099 3098/3047/3098 +f 3099/3048/3099 3109/3058/3109 3110/3059/3110 +f 3110/3059/3110 3100/3049/3100 3099/3048/3099 +f 3100/3049/3100 3110/3059/3110 3111/3060/3111 +f 3111/3060/3111 3101/3050/3101 3100/3049/3100 +f 2711/2660/2711 2721/2670/2721 3112/3061/3112 +f 3112/3061/3112 3102/3051/3102 2711/2660/2711 +f 3102/3051/3102 3112/3061/3112 3113/3062/3113 +f 3113/3062/3113 3103/3052/3103 3102/3051/3102 +f 3103/3052/3103 3113/3062/3113 3114/3063/3114 +f 3114/3063/3114 3104/3053/3104 3103/3052/3103 +f 3104/3053/3104 3114/3063/3114 3115/3064/3115 +f 3115/3064/3115 3105/3054/3105 3104/3053/3104 +f 3105/3054/3105 3115/3064/3115 3116/3065/3116 +f 3116/3065/3116 3106/3055/3106 3105/3054/3105 +f 3106/3055/3106 3116/3065/3116 3117/3066/3117 +f 3117/3066/3117 3107/3056/3107 3106/3055/3106 +f 3107/3056/3107 3117/3066/3117 3118/3067/3118 +f 3118/3067/3118 3108/3057/3108 3107/3056/3107 +f 3108/3057/3108 3118/3067/3118 3119/3068/3119 +f 3119/3068/3119 3109/3058/3109 3108/3057/3108 +f 3109/3058/3109 3119/3068/3119 3120/3069/3120 +f 3120/3069/3120 3110/3059/3110 3109/3058/3109 +f 3110/3059/3110 3120/3069/3120 3121/3070/3121 +f 3121/3070/3121 3111/3060/3111 3110/3059/3110 +f 2721/2670/2721 2731/2680/2731 3122/3071/3122 +f 3122/3071/3122 3112/3061/3112 2721/2670/2721 +f 3112/3061/3112 3122/3071/3122 3123/3072/3123 +f 3123/3072/3123 3113/3062/3113 3112/3061/3112 +f 3113/3062/3113 3123/3072/3123 3124/3073/3124 +f 3124/3073/3124 3114/3063/3114 3113/3062/3113 +f 3114/3063/3114 3124/3073/3124 3125/3074/3125 +f 3125/3074/3125 3115/3064/3115 3114/3063/3114 +f 3115/3064/3115 3125/3074/3125 3126/3075/3126 +f 3126/3075/3126 3116/3065/3116 3115/3064/3115 +f 3116/3065/3116 3126/3075/3126 3127/3076/3127 +f 3127/3076/3127 3117/3066/3117 3116/3065/3116 +f 3117/3066/3117 3127/3076/3127 3128/3077/3128 +f 3128/3077/3128 3118/3067/3118 3117/3066/3117 +f 3118/3067/3118 3128/3077/3128 3129/3078/3129 +f 3129/3078/3129 3119/3068/3119 3118/3067/3118 +f 3119/3068/3119 3129/3078/3129 3130/3079/3130 +f 3130/3079/3130 3120/3069/3120 3119/3068/3119 +f 3120/3069/3120 3130/3079/3130 3131/3080/3131 +f 3131/3080/3131 3121/3070/3121 3120/3069/3120 +f 2731/2680/2731 2741/2690/2741 3132/3081/3132 +f 3132/3081/3132 3122/3071/3122 2731/2680/2731 +f 3122/3071/3122 3132/3081/3132 3133/3082/3133 +f 3133/3082/3133 3123/3072/3123 3122/3071/3122 +f 3123/3072/3123 3133/3082/3133 3134/3083/3134 +f 3134/3083/3134 3124/3073/3124 3123/3072/3123 +f 3124/3073/3124 3134/3083/3134 3135/3084/3135 +f 3135/3084/3135 3125/3074/3125 3124/3073/3124 +f 3125/3074/3125 3135/3084/3135 3136/3085/3136 +f 3136/3085/3136 3126/3075/3126 3125/3074/3125 +f 3126/3075/3126 3136/3085/3136 3137/3086/3137 +f 3137/3086/3137 3127/3076/3127 3126/3075/3126 +f 3127/3076/3127 3137/3086/3137 3138/3087/3138 +f 3138/3087/3138 3128/3077/3128 3127/3076/3127 +f 3128/3077/3128 3138/3087/3138 3139/3088/3139 +f 3139/3088/3139 3129/3078/3129 3128/3077/3128 +f 3129/3078/3129 3139/3088/3139 3140/3089/3140 +f 3140/3089/3140 3130/3079/3130 3129/3078/3129 +f 3130/3079/3130 3140/3089/3140 3141/3090/3141 +f 3141/3090/3141 3131/3080/3131 3130/3079/3130 +f 2741/2690/2741 2751/2700/2751 3142/3091/3142 +f 3142/3091/3142 3132/3081/3132 2741/2690/2741 +f 3132/3081/3132 3142/3091/3142 3143/3092/3143 +f 3143/3092/3143 3133/3082/3133 3132/3081/3132 +f 3133/3082/3133 3143/3092/3143 3144/3093/3144 +f 3144/3093/3144 3134/3083/3134 3133/3082/3133 +f 3134/3083/3134 3144/3093/3144 3145/3094/3145 +f 3145/3094/3145 3135/3084/3135 3134/3083/3134 +f 3135/3084/3135 3145/3094/3145 3146/3095/3146 +f 3146/3095/3146 3136/3085/3136 3135/3084/3135 +f 3136/3085/3136 3146/3095/3146 3147/3096/3147 +f 3147/3096/3147 3137/3086/3137 3136/3085/3136 +f 3137/3086/3137 3147/3096/3147 3148/3097/3148 +f 3148/3097/3148 3138/3087/3138 3137/3086/3137 +f 3138/3087/3138 3148/3097/3148 3149/3098/3149 +f 3149/3098/3149 3139/3088/3139 3138/3087/3138 +f 3139/3088/3139 3149/3098/3149 3150/3099/3150 +f 3150/3099/3150 3140/3089/3140 3139/3088/3139 +f 3140/3089/3140 3150/3099/3150 3151/3100/3151 +f 3151/3100/3151 3141/3090/3141 3140/3089/3140 +f 2751/2700/2751 2761/2710/2761 3152/3101/3152 +f 3152/3101/3152 3142/3091/3142 2751/2700/2751 +f 3142/3091/3142 3152/3101/3152 3153/3102/3153 +f 3153/3102/3153 3143/3092/3143 3142/3091/3142 +f 3143/3092/3143 3153/3102/3153 3154/3103/3154 +f 3154/3103/3154 3144/3093/3144 3143/3092/3143 +f 3144/3093/3144 3154/3103/3154 3155/3104/3155 +f 3155/3104/3155 3145/3094/3145 3144/3093/3144 +f 3145/3094/3145 3155/3104/3155 3156/3105/3156 +f 3156/3105/3156 3146/3095/3146 3145/3094/3145 +f 3146/3095/3146 3156/3105/3156 3157/3106/3157 +f 3157/3106/3157 3147/3096/3147 3146/3095/3146 +f 3147/3096/3147 3157/3106/3157 3158/3107/3158 +f 3158/3107/3158 3148/3097/3148 3147/3096/3147 +f 3148/3097/3148 3158/3107/3158 3159/3108/3159 +f 3159/3108/3159 3149/3098/3149 3148/3097/3148 +f 3149/3098/3149 3159/3108/3159 3160/3109/3160 +f 3160/3109/3160 3150/3099/3150 3149/3098/3149 +f 3150/3099/3150 3160/3109/3160 3161/3110/3161 +f 3161/3110/3161 3151/3100/3151 3150/3099/3150 +f 2761/2710/2761 2771/2720/2771 3162/3111/3162 +f 3162/3111/3162 3152/3101/3152 2761/2710/2761 +f 3152/3101/3152 3162/3111/3162 3163/3112/3163 +f 3163/3112/3163 3153/3102/3153 3152/3101/3152 +f 3153/3102/3153 3163/3112/3163 3164/3113/3164 +f 3164/3113/3164 3154/3103/3154 3153/3102/3153 +f 3154/3103/3154 3164/3113/3164 3165/3114/3165 +f 3165/3114/3165 3155/3104/3155 3154/3103/3154 +f 3155/3104/3155 3165/3114/3165 3166/3115/3166 +f 3166/3115/3166 3156/3105/3156 3155/3104/3155 +f 3156/3105/3156 3166/3115/3166 3167/3116/3167 +f 3167/3116/3167 3157/3106/3157 3156/3105/3156 +f 3157/3106/3157 3167/3116/3167 3168/3117/3168 +f 3168/3117/3168 3158/3107/3158 3157/3106/3157 +f 3158/3107/3158 3168/3117/3168 3169/3118/3169 +f 3169/3118/3169 3159/3108/3159 3158/3107/3158 +f 3159/3108/3159 3169/3118/3169 3170/3119/3170 +f 3170/3119/3170 3160/3109/3160 3159/3108/3159 +f 3160/3109/3160 3170/3119/3170 3171/3120/3171 +f 3171/3120/3171 3161/3110/3161 3160/3109/3160 +f 2771/2720/2771 2781/2730/2781 3172/3121/3172 +f 3172/3121/3172 3162/3111/3162 2771/2720/2771 +f 3162/3111/3162 3172/3121/3172 3173/3122/3173 +f 3173/3122/3173 3163/3112/3163 3162/3111/3162 +f 3163/3112/3163 3173/3122/3173 3174/3123/3174 +f 3174/3123/3174 3164/3113/3164 3163/3112/3163 +f 3164/3113/3164 3174/3123/3174 3175/3124/3175 +f 3175/3124/3175 3165/3114/3165 3164/3113/3164 +f 3165/3114/3165 3175/3124/3175 3176/3125/3176 +f 3176/3125/3176 3166/3115/3166 3165/3114/3165 +f 3166/3115/3166 3176/3125/3176 3177/3126/3177 +f 3177/3126/3177 3167/3116/3167 3166/3115/3166 +f 3167/3116/3167 3177/3126/3177 3178/3127/3178 +f 3178/3127/3178 3168/3117/3168 3167/3116/3167 +f 3168/3117/3168 3178/3127/3178 3179/3128/3179 +f 3179/3128/3179 3169/3118/3169 3168/3117/3168 +f 3169/3118/3169 3179/3128/3179 3180/3129/3180 +f 3180/3129/3180 3170/3119/3170 3169/3118/3169 +f 3170/3119/3170 3180/3129/3180 3181/3130/3181 +f 3181/3130/3181 3171/3120/3171 3170/3119/3170 +f 2781/2730/2781 2791/2740/2791 3182/3131/3182 +f 3182/3131/3182 3172/3121/3172 2781/2730/2781 +f 3172/3121/3172 3182/3131/3182 3183/3132/3183 +f 3183/3132/3183 3173/3122/3173 3172/3121/3172 +f 3173/3122/3173 3183/3132/3183 3184/3133/3184 +f 3184/3133/3184 3174/3123/3174 3173/3122/3173 +f 3174/3123/3174 3184/3133/3184 3185/3134/3185 +f 3185/3134/3185 3175/3124/3175 3174/3123/3174 +f 3175/3124/3175 3185/3134/3185 3186/3135/3186 +f 3186/3135/3186 3176/3125/3176 3175/3124/3175 +f 3176/3125/3176 3186/3135/3186 3187/3136/3187 +f 3187/3136/3187 3177/3126/3177 3176/3125/3176 +f 3177/3126/3177 3187/3136/3187 3188/3137/3188 +f 3188/3137/3188 3178/3127/3178 3177/3126/3177 +f 3178/3127/3178 3188/3137/3188 3189/3138/3189 +f 3189/3138/3189 3179/3128/3179 3178/3127/3178 +f 3179/3128/3179 3189/3138/3189 3190/3139/3190 +f 3190/3139/3190 3180/3129/3180 3179/3128/3179 +f 3180/3129/3180 3190/3139/3190 3191/3140/3191 +f 3191/3140/3191 3181/3130/3181 3180/3129/3180 +f 2791/2740/2791 2801/2750/2801 3192/3141/3192 +f 3192/3141/3192 3182/3131/3182 2791/2740/2791 +f 3182/3131/3182 3192/3141/3192 3193/3142/3193 +f 3193/3142/3193 3183/3132/3183 3182/3131/3182 +f 3183/3132/3183 3193/3142/3193 3194/3143/3194 +f 3194/3143/3194 3184/3133/3184 3183/3132/3183 +f 3184/3133/3184 3194/3143/3194 3195/3144/3195 +f 3195/3144/3195 3185/3134/3185 3184/3133/3184 +f 3185/3134/3185 3195/3144/3195 3196/3145/3196 +f 3196/3145/3196 3186/3135/3186 3185/3134/3185 +f 3186/3135/3186 3196/3145/3196 3197/3146/3197 +f 3197/3146/3197 3187/3136/3187 3186/3135/3186 +f 3187/3136/3187 3197/3146/3197 3198/3147/3198 +f 3198/3147/3198 3188/3137/3188 3187/3136/3187 +f 3188/3137/3188 3198/3147/3198 3199/3148/3199 +f 3199/3148/3199 3189/3138/3189 3188/3137/3188 +f 3189/3138/3189 3199/3148/3199 3200/3149/3200 +f 3200/3149/3200 3190/3139/3190 3189/3138/3189 +f 3190/3139/3190 3200/3149/3200 3201/3150/3201 +f 3201/3150/3201 3191/3140/3191 3190/3139/3190 +f 2801/2750/2801 2811/2760/2811 3202/3151/3202 +f 3202/3151/3202 3192/3141/3192 2801/2750/2801 +f 3192/3141/3192 3202/3151/3202 3203/3152/3203 +f 3203/3152/3203 3193/3142/3193 3192/3141/3192 +f 3193/3142/3193 3203/3152/3203 3204/3153/3204 +f 3204/3153/3204 3194/3143/3194 3193/3142/3193 +f 3194/3143/3194 3204/3153/3204 3205/3154/3205 +f 3205/3154/3205 3195/3144/3195 3194/3143/3194 +f 3195/3144/3195 3205/3154/3205 3206/3155/3206 +f 3206/3155/3206 3196/3145/3196 3195/3144/3195 +f 3196/3145/3196 3206/3155/3206 3207/3156/3207 +f 3207/3156/3207 3197/3146/3197 3196/3145/3196 +f 3197/3146/3197 3207/3156/3207 3208/3157/3208 +f 3208/3157/3208 3198/3147/3198 3197/3146/3197 +f 3198/3147/3198 3208/3157/3208 3209/3158/3209 +f 3209/3158/3209 3199/3148/3199 3198/3147/3198 +f 3199/3148/3199 3209/3158/3209 3210/3159/3210 +f 3210/3159/3210 3200/3149/3200 3199/3148/3199 +f 3200/3149/3200 3210/3159/3210 3211/3160/3211 +f 3211/3160/3211 3201/3150/3201 3200/3149/3200 +f 2811/2760/2811 2821/2770/2821 3212/3161/3212 +f 3212/3161/3212 3202/3151/3202 2811/2760/2811 +f 3202/3151/3202 3212/3161/3212 3213/3162/3213 +f 3213/3162/3213 3203/3152/3203 3202/3151/3202 +f 3203/3152/3203 3213/3162/3213 3214/3163/3214 +f 3214/3163/3214 3204/3153/3204 3203/3152/3203 +f 3204/3153/3204 3214/3163/3214 3215/3164/3215 +f 3215/3164/3215 3205/3154/3205 3204/3153/3204 +f 3205/3154/3205 3215/3164/3215 3216/3165/3216 +f 3216/3165/3216 3206/3155/3206 3205/3154/3205 +f 3206/3155/3206 3216/3165/3216 3217/3166/3217 +f 3217/3166/3217 3207/3156/3207 3206/3155/3206 +f 3207/3156/3207 3217/3166/3217 3218/3167/3218 +f 3218/3167/3218 3208/3157/3208 3207/3156/3207 +f 3208/3157/3208 3218/3167/3218 3219/3168/3219 +f 3219/3168/3219 3209/3158/3209 3208/3157/3208 +f 3209/3158/3209 3219/3168/3219 3220/3169/3220 +f 3220/3169/3220 3210/3159/3210 3209/3158/3209 +f 3210/3159/3210 3220/3169/3220 3221/3170/3221 +f 3221/3170/3221 3211/3160/3211 3210/3159/3210 +f 2821/2770/2821 2831/2780/2831 3222/3171/3222 +f 3222/3171/3222 3212/3161/3212 2821/2770/2821 +f 3212/3161/3212 3222/3171/3222 3223/3172/3223 +f 3223/3172/3223 3213/3162/3213 3212/3161/3212 +f 3213/3162/3213 3223/3172/3223 3224/3173/3224 +f 3224/3173/3224 3214/3163/3214 3213/3162/3213 +f 3214/3163/3214 3224/3173/3224 3225/3174/3225 +f 3225/3174/3225 3215/3164/3215 3214/3163/3214 +f 3215/3164/3215 3225/3174/3225 3226/3175/3226 +f 3226/3175/3226 3216/3165/3216 3215/3164/3215 +f 3216/3165/3216 3226/3175/3226 3227/3176/3227 +f 3227/3176/3227 3217/3166/3217 3216/3165/3216 +f 3217/3166/3217 3227/3176/3227 3228/3177/3228 +f 3228/3177/3228 3218/3167/3218 3217/3166/3217 +f 3218/3167/3218 3228/3177/3228 3229/3178/3229 +f 3229/3178/3229 3219/3168/3219 3218/3167/3218 +f 3219/3168/3219 3229/3178/3229 3230/3179/3230 +f 3230/3179/3230 3220/3169/3220 3219/3168/3219 +f 3220/3169/3220 3230/3179/3230 3231/3180/3231 +f 3231/3180/3231 3221/3170/3221 3220/3169/3220 +f 2831/2780/2831 2841/2790/2841 3232/3181/3232 +f 3232/3181/3232 3222/3171/3222 2831/2780/2831 +f 3222/3171/3222 3232/3181/3232 3233/3182/3233 +f 3233/3182/3233 3223/3172/3223 3222/3171/3222 +f 3223/3172/3223 3233/3182/3233 3234/3183/3234 +f 3234/3183/3234 3224/3173/3224 3223/3172/3223 +f 3224/3173/3224 3234/3183/3234 3235/3184/3235 +f 3235/3184/3235 3225/3174/3225 3224/3173/3224 +f 3225/3174/3225 3235/3184/3235 3236/3185/3236 +f 3236/3185/3236 3226/3175/3226 3225/3174/3225 +f 3226/3175/3226 3236/3185/3236 3237/3186/3237 +f 3237/3186/3237 3227/3176/3227 3226/3175/3226 +f 3227/3176/3227 3237/3186/3237 3238/3187/3238 +f 3238/3187/3238 3228/3177/3228 3227/3176/3227 +f 3228/3177/3228 3238/3187/3238 3239/3188/3239 +f 3239/3188/3239 3229/3178/3229 3228/3177/3228 +f 3229/3178/3229 3239/3188/3239 3240/3189/3240 +f 3240/3189/3240 3230/3179/3230 3229/3178/3229 +f 3230/3179/3230 3240/3189/3240 3241/3190/3241 +f 3241/3190/3241 3231/3180/3231 3230/3179/3230 +f 2841/2790/2841 2461/2410/2461 2843/2792/2843 +f 2843/2792/2843 3232/3181/3232 2841/2790/2841 +f 3232/3181/3232 2843/2792/2843 2845/2794/2845 +f 2845/2794/2845 3233/3182/3233 3232/3181/3232 +f 3233/3182/3233 2845/2794/2845 2847/2796/2847 +f 2847/2796/2847 3234/3183/3234 3233/3182/3233 +f 3234/3183/3234 2847/2796/2847 2849/2798/2849 +f 2849/2798/2849 3235/3184/3235 3234/3183/3234 +f 3235/3184/3235 2849/2798/2849 2851/2800/2851 +f 2851/2800/2851 3236/3185/3236 3235/3184/3235 +f 3236/3185/3236 2851/2800/2851 2853/2802/2853 +f 2853/2802/2853 3237/3186/3237 3236/3185/3236 +f 3237/3186/3237 2853/2802/2853 2855/2804/2855 +f 2855/2804/2855 3238/3187/3238 3237/3186/3237 +f 3238/3187/3238 2855/2804/2855 2857/2806/2857 +f 2857/2806/2857 3239/3188/3239 3238/3187/3238 +f 3239/3188/3239 2857/2806/2857 2859/2808/2859 +f 2859/2808/2859 3240/3189/3240 3239/3188/3239 +f 3240/3189/3240 2859/2808/2859 2861/2810/2861 +f 2861/2810/2861 3241/3190/3241 3240/3189/3240 diff --git a/engine/src/test-data/Models/Terrain/Terrain.mesh.xml b/engine/src/test-data/Models/Terrain/Terrain.mesh.xml new file mode 100644 index 000000000..611fd9ef4 --- /dev/null +++ b/engine/src/test-data/Models/Terrain/Terrain.mesh.xml @@ -0,0 +1,115985 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Tree/BarkColor.png b/engine/src/test-data/Models/Tree/BarkColor.png new file mode 100644 index 000000000..8bb4b00a9 Binary files /dev/null and b/engine/src/test-data/Models/Tree/BarkColor.png differ diff --git a/engine/src/test-data/Models/Tree/BarkNormal.png b/engine/src/test-data/Models/Tree/BarkNormal.png new file mode 100644 index 000000000..1b3c3888e Binary files /dev/null and b/engine/src/test-data/Models/Tree/BarkNormal.png differ diff --git a/engine/src/test-data/Models/Tree/Leaves.j3m b/engine/src/test-data/Models/Tree/Leaves.j3m new file mode 100644 index 000000000..fddc2f75c --- /dev/null +++ b/engine/src/test-data/Models/Tree/Leaves.j3m @@ -0,0 +1,20 @@ +Material Leaves : Common/MatDefs/Light/Lighting.j3md { + + Transparent On + + MaterialParameters { + DiffuseMap : Models/Tree/Leaves.png + UseAlpha : true + AlphaDiscardThreshold : 0.5 + UseMaterialColors : true + Ambient : .5 .5 .5 .5 + Diffuse : 0.7 0.7 0.7 1 + Specular : 0 0 0 1 + Shininess : 16 + } + AdditionalRenderState { + Blend Alpha + AlphaTestFalloff 0.50 + FaceCull Off + } +} \ No newline at end of file diff --git a/engine/src/test-data/Models/Tree/Leaves.png b/engine/src/test-data/Models/Tree/Leaves.png new file mode 100644 index 000000000..a3a9f8e33 Binary files /dev/null and b/engine/src/test-data/Models/Tree/Leaves.png differ diff --git a/engine/src/test-data/Models/Tree/Tree2.mesh.xml b/engine/src/test-data/Models/Tree/Tree2.mesh.xml new file mode 100644 index 000000000..3c197b86c --- /dev/null +++ b/engine/src/test-data/Models/Tree/Tree2.mesh.xml @@ -0,0 +1,20727 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Models/Tree/Trunk.j3m b/engine/src/test-data/Models/Tree/Trunk.j3m new file mode 100644 index 000000000..a66dcdf33 --- /dev/null +++ b/engine/src/test-data/Models/Tree/Trunk.j3m @@ -0,0 +1,11 @@ +Material Trunk : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Repeat Models/Tree/BarkColor.png + NormalMap : Repeat Models/Tree/BarkNormal.png + UseMaterialColors : true + Ambient : 0 0 0 1 + Diffuse : 1 1 1 1 + Specular : 0 0 0 1 + Shininess : 16 + } +} \ No newline at end of file diff --git a/engine/src/test-data/Models/WaterTest/WaterTest.mesh.xml b/engine/src/test-data/Models/WaterTest/WaterTest.mesh.xml new file mode 100644 index 000000000..6908e62aa --- /dev/null +++ b/engine/src/test-data/Models/WaterTest/WaterTest.mesh.xml @@ -0,0 +1,7505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Scenes/Beach/Beach2.mesh.j3o b/engine/src/test-data/Scenes/Beach/Beach2.mesh.j3o new file mode 100644 index 000000000..780729c2f Binary files /dev/null and b/engine/src/test-data/Scenes/Beach/Beach2.mesh.j3o differ diff --git a/engine/src/test-data/Scenes/Beach/Beach2.mesh.xml b/engine/src/test-data/Scenes/Beach/Beach2.mesh.xml new file mode 100644 index 000000000..ef5e4448b --- /dev/null +++ b/engine/src/test-data/Scenes/Beach/Beach2.mesh.xml @@ -0,0 +1,29329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Scenes/Beach/FullskiesSunset0068.dds b/engine/src/test-data/Scenes/Beach/FullskiesSunset0068.dds new file mode 100644 index 000000000..6d5e0d8e8 Binary files /dev/null and b/engine/src/test-data/Scenes/Beach/FullskiesSunset0068.dds differ diff --git a/engine/src/test-data/Scenes/Beach/Sand.jpg b/engine/src/test-data/Scenes/Beach/Sand.jpg new file mode 100644 index 000000000..2699b82a0 Binary files /dev/null and b/engine/src/test-data/Scenes/Beach/Sand.jpg differ diff --git a/engine/src/test-data/Scenes/Beach/Sand_Normal.png b/engine/src/test-data/Scenes/Beach/Sand_Normal.png new file mode 100644 index 000000000..91658b50a Binary files /dev/null and b/engine/src/test-data/Scenes/Beach/Sand_Normal.png differ diff --git a/engine/src/test-data/Scenes/Beach/sand.j3m b/engine/src/test-data/Scenes/Beach/sand.j3m new file mode 100644 index 000000000..f1110de22 --- /dev/null +++ b/engine/src/test-data/Scenes/Beach/sand.j3m @@ -0,0 +1,11 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Scenes/Beach/Sand.jpg + NormalMap : Scenes/Beach/Sand_Normal.png + UseMaterialColors : true + Ambient : 0 0 0 1 + Diffuse : 1 1 1 1 + Specular : 0 0 0 1 + Shininess : 16 + } +} diff --git a/engine/src/test-data/Scenes/DotScene/DotScene.scene b/engine/src/test-data/Scenes/DotScene/DotScene.scene new file mode 100644 index 000000000..dea6c15b3 --- /dev/null +++ b/engine/src/test-data/Scenes/DotScene/DotScene.scene @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/engine/src/test-data/Scenes/ManyLights/AO.dds b/engine/src/test-data/Scenes/ManyLights/AO.dds new file mode 100644 index 000000000..c5883a43c Binary files /dev/null and b/engine/src/test-data/Scenes/ManyLights/AO.dds differ diff --git a/engine/src/test-data/Scenes/ManyLights/AO.jpg b/engine/src/test-data/Scenes/ManyLights/AO.jpg new file mode 100644 index 000000000..97dddc27a Binary files /dev/null and b/engine/src/test-data/Scenes/ManyLights/AO.jpg differ diff --git a/engine/src/test-data/Scenes/ManyLights/AO.tga b/engine/src/test-data/Scenes/ManyLights/AO.tga new file mode 100644 index 000000000..40333503a Binary files /dev/null and b/engine/src/test-data/Scenes/ManyLights/AO.tga differ diff --git a/engine/src/test-data/Scenes/ManyLights/Grid.mesh.xml b/engine/src/test-data/Scenes/ManyLights/Grid.mesh.xml new file mode 100644 index 000000000..f5d75e44e --- /dev/null +++ b/engine/src/test-data/Scenes/ManyLights/Grid.mesh.xml @@ -0,0 +1,7162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Scenes/ManyLights/Main.material b/engine/src/test-data/Scenes/ManyLights/Main.material new file mode 100644 index 000000000..c12a65695 --- /dev/null +++ b/engine/src/test-data/Scenes/ManyLights/Main.material @@ -0,0 +1,20 @@ +material Material +{ + receive_shadows off + technique + { + pass + { + ambient 0.000000 0.000000 0.000000 1.000000 + diffuse 1.000000 1.000000 1.000000 1.000000 + specular 0.000000 0.000000 0.000000 1.000000 0.250000 + emissive 0.000000 0.000000 0.000000 1.000000 + texture_unit + { + texture AO.tga + tex_address_mode wrap + filtering trilinear + } + } + } +} diff --git a/engine/src/test-data/Scenes/ManyLights/Main.scene b/engine/src/test-data/Scenes/ManyLights/Main.scene new file mode 100644 index 000000000..eb260fbc3 --- /dev/null +++ b/engine/src/test-data/Scenes/ManyLights/Main.scene @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test-data/Sound/Effects/Bang.wav b/engine/src/test-data/Sound/Effects/Bang.wav new file mode 100644 index 000000000..4bfb435e7 Binary files /dev/null and b/engine/src/test-data/Sound/Effects/Bang.wav differ diff --git a/engine/src/test-data/Sound/Effects/Beep.ogg b/engine/src/test-data/Sound/Effects/Beep.ogg new file mode 100644 index 000000000..20dd441a9 Binary files /dev/null and b/engine/src/test-data/Sound/Effects/Beep.ogg differ diff --git a/engine/src/test-data/Sound/Effects/Foot steps.ogg b/engine/src/test-data/Sound/Effects/Foot steps.ogg new file mode 100644 index 000000000..d10d3c793 Binary files /dev/null and b/engine/src/test-data/Sound/Effects/Foot steps.ogg differ diff --git a/engine/src/test-data/Sound/Effects/Gun.wav b/engine/src/test-data/Sound/Effects/Gun.wav new file mode 100644 index 000000000..eecc4d107 Binary files /dev/null and b/engine/src/test-data/Sound/Effects/Gun.wav differ diff --git a/engine/src/test-data/Sound/Effects/kick.wav b/engine/src/test-data/Sound/Effects/kick.wav new file mode 100644 index 000000000..6a3f26f1f Binary files /dev/null and b/engine/src/test-data/Sound/Effects/kick.wav differ diff --git a/engine/src/test-data/Sound/Environment/Nature.ogg b/engine/src/test-data/Sound/Environment/Nature.ogg new file mode 100644 index 000000000..e98d73e45 Binary files /dev/null and b/engine/src/test-data/Sound/Environment/Nature.ogg differ diff --git a/engine/src/test-data/Sound/Environment/Ocean Waves.ogg b/engine/src/test-data/Sound/Environment/Ocean Waves.ogg new file mode 100644 index 000000000..a52c96c3f Binary files /dev/null and b/engine/src/test-data/Sound/Environment/Ocean Waves.ogg differ diff --git a/engine/src/test-data/Sound/Environment/River.ogg b/engine/src/test-data/Sound/Environment/River.ogg new file mode 100644 index 000000000..e213fe394 Binary files /dev/null and b/engine/src/test-data/Sound/Environment/River.ogg differ diff --git a/engine/src/test-data/Textures/BumpMapTest/Dot3.jpg b/engine/src/test-data/Textures/BumpMapTest/Dot3.jpg new file mode 100644 index 000000000..9240dd1df Binary files /dev/null and b/engine/src/test-data/Textures/BumpMapTest/Dot3.jpg differ diff --git a/engine/src/test-data/Textures/BumpMapTest/Dot3_dxt1.dds b/engine/src/test-data/Textures/BumpMapTest/Dot3_dxt1.dds new file mode 100644 index 000000000..8199cedd3 Binary files /dev/null and b/engine/src/test-data/Textures/BumpMapTest/Dot3_dxt1.dds differ diff --git a/engine/src/test-data/Textures/BumpMapTest/Dot3_latc.dds b/engine/src/test-data/Textures/BumpMapTest/Dot3_latc.dds new file mode 100644 index 000000000..26302f5cf Binary files /dev/null and b/engine/src/test-data/Textures/BumpMapTest/Dot3_latc.dds differ diff --git a/engine/src/test-data/Textures/BumpMapTest/SimpleBump.j3m b/engine/src/test-data/Textures/BumpMapTest/SimpleBump.j3m new file mode 100644 index 000000000..b23e10a64 --- /dev/null +++ b/engine/src/test-data/Textures/BumpMapTest/SimpleBump.j3m @@ -0,0 +1,10 @@ +Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 8.0 + NormalMap: Textures/BumpMapTest/Simple_normal.png + UseMaterialColors : true + Ambient : 0 0 0 1 + Diffuse : 1 1 1 1 + Specular : 0 0 0 1 + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/BumpMapTest/Simple_height.png b/engine/src/test-data/Textures/BumpMapTest/Simple_height.png new file mode 100644 index 000000000..f396670d8 Binary files /dev/null and b/engine/src/test-data/Textures/BumpMapTest/Simple_height.png differ diff --git a/engine/src/test-data/Textures/BumpMapTest/Simple_normal.png b/engine/src/test-data/Textures/BumpMapTest/Simple_normal.png new file mode 100644 index 000000000..e03ef00d8 Binary files /dev/null and b/engine/src/test-data/Textures/BumpMapTest/Simple_normal.png differ diff --git a/engine/src/test-data/Textures/BumpMapTest/Tangent.j3m b/engine/src/test-data/Textures/BumpMapTest/Tangent.j3m new file mode 100644 index 000000000..851a0e9e9 --- /dev/null +++ b/engine/src/test-data/Textures/BumpMapTest/Tangent.j3m @@ -0,0 +1,6 @@ +Material TangentBinormal : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 8.0 + DiffuseMap: Textures/BumpMapTest/Tangent.png + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/BumpMapTest/Tangent.png b/engine/src/test-data/Textures/BumpMapTest/Tangent.png new file mode 100644 index 000000000..0c9a3ce2f Binary files /dev/null and b/engine/src/test-data/Textures/BumpMapTest/Tangent.png differ diff --git a/engine/src/test-data/Textures/ColorRamp/cloudy.png b/engine/src/test-data/Textures/ColorRamp/cloudy.png new file mode 100644 index 000000000..169e98118 Binary files /dev/null and b/engine/src/test-data/Textures/ColorRamp/cloudy.png differ diff --git a/engine/src/test-data/Textures/ColorRamp/toon.png b/engine/src/test-data/Textures/ColorRamp/toon.png new file mode 100644 index 000000000..70591884f Binary files /dev/null and b/engine/src/test-data/Textures/ColorRamp/toon.png differ diff --git a/engine/src/test-data/Textures/ColoredTex/Monkey.png b/engine/src/test-data/Textures/ColoredTex/Monkey.png new file mode 100644 index 000000000..eafa03fde Binary files /dev/null and b/engine/src/test-data/Textures/ColoredTex/Monkey.png differ diff --git a/engine/src/test-data/Textures/HdrTest/Memorial.hdr b/engine/src/test-data/Textures/HdrTest/Memorial.hdr new file mode 100644 index 000000000..97919bf81 Binary files /dev/null and b/engine/src/test-data/Textures/HdrTest/Memorial.hdr differ diff --git a/engine/src/test-data/Textures/HdrTest/Memorial.j3m b/engine/src/test-data/Textures/HdrTest/Memorial.j3m new file mode 100644 index 000000000..9087aa99a --- /dev/null +++ b/engine/src/test-data/Textures/HdrTest/Memorial.j3m @@ -0,0 +1,5 @@ +Material HDR Picture : Common/MatDefs/Misc/SimpleTextured.j3md { + MaterialParameters { + ColorMap : Flip Textures/HdrTest/Memorial.hdr + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/Sky/Bright/BrightSky.dds b/engine/src/test-data/Textures/Sky/Bright/BrightSky.dds new file mode 100644 index 000000000..3379320ab Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Bright/BrightSky.dds differ diff --git a/engine/src/test-data/Textures/Sky/Bright/FullskiesBlueClear03.dds b/engine/src/test-data/Textures/Sky/Bright/FullskiesBlueClear03.dds new file mode 100644 index 000000000..1a86a1fda Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Bright/FullskiesBlueClear03.dds differ diff --git a/engine/src/test-data/Textures/Sky/Lagoon/LICENSE.txt b/engine/src/test-data/Textures/Sky/Lagoon/LICENSE.txt new file mode 100644 index 000000000..9685769b1 --- /dev/null +++ b/engine/src/test-data/Textures/Sky/Lagoon/LICENSE.txt @@ -0,0 +1,7 @@ +Licensed under CC-BY-NC +http://creativecommons.org/licenses/by-nc/3.0/ + +"The textures on this page are for non-commercial purposes only. +You're free to use and modify them otherwise." + +http://www.hazelwhorley.com/skyboxtex2_bitmaps.html \ No newline at end of file diff --git a/engine/src/test-data/Textures/Sky/Lagoon/lagoon_down.jpg b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_down.jpg new file mode 100644 index 000000000..c9f5e16e3 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_down.jpg differ diff --git a/engine/src/test-data/Textures/Sky/Lagoon/lagoon_east.jpg b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_east.jpg new file mode 100644 index 000000000..1eb30a715 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_east.jpg differ diff --git a/engine/src/test-data/Textures/Sky/Lagoon/lagoon_north.jpg b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_north.jpg new file mode 100644 index 000000000..be769e595 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_north.jpg differ diff --git a/engine/src/test-data/Textures/Sky/Lagoon/lagoon_south.jpg b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_south.jpg new file mode 100644 index 000000000..ec28db88f Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_south.jpg differ diff --git a/engine/src/test-data/Textures/Sky/Lagoon/lagoon_up.jpg b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_up.jpg new file mode 100644 index 000000000..3128f76d1 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_up.jpg differ diff --git a/engine/src/test-data/Textures/Sky/Lagoon/lagoon_west.jpg b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_west.jpg new file mode 100644 index 000000000..5528fd8c0 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Lagoon/lagoon_west.jpg differ diff --git a/engine/src/test-data/Textures/Sky/Night/Night.png b/engine/src/test-data/Textures/Sky/Night/Night.png new file mode 100644 index 000000000..7172bf164 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Night/Night.png differ diff --git a/engine/src/test-data/Textures/Sky/Night/Night_dxt1.dds b/engine/src/test-data/Textures/Sky/Night/Night_dxt1.dds new file mode 100644 index 000000000..7066dae9e Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Night/Night_dxt1.dds differ diff --git a/engine/src/test-data/Textures/Sky/Night/Night_ycc.dds b/engine/src/test-data/Textures/Sky/Night/Night_ycc.dds new file mode 100644 index 000000000..063ae3079 Binary files /dev/null and b/engine/src/test-data/Textures/Sky/Night/Night_ycc.dds differ diff --git a/engine/src/test-data/Textures/Sky/St Peters/StPeters.hdr b/engine/src/test-data/Textures/Sky/St Peters/StPeters.hdr new file mode 100644 index 000000000..5283a6dad Binary files /dev/null and b/engine/src/test-data/Textures/Sky/St Peters/StPeters.hdr differ diff --git a/engine/src/test-data/Textures/Sky/St Peters/StPeters.jpg b/engine/src/test-data/Textures/Sky/St Peters/StPeters.jpg new file mode 100644 index 000000000..4d46cef7a Binary files /dev/null and b/engine/src/test-data/Textures/Sky/St Peters/StPeters.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall.j3m b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall.j3m new file mode 100644 index 000000000..8b54f9e39 --- /dev/null +++ b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall.j3m @@ -0,0 +1,8 @@ +Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 2.0 + DiffuseMap : Textures/Terrain/BrickWall/BrickWall.jpg + NormalMap : Textures/Terrain/BrickWall/BrickWall_normal.jpg + ParallaxMap : Textures/Terrain/BrickWall/BrickWall_height.jpg + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall.jpg b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall.jpg new file mode 100644 index 000000000..c386a0d6c Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_height.jpg b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_height.jpg new file mode 100644 index 000000000..3486674aa Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_height.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg new file mode 100644 index 000000000..80ce82efb Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/Pond/Pond.j3m b/engine/src/test-data/Textures/Terrain/Pond/Pond.j3m new file mode 100644 index 000000000..3ae6f8243 --- /dev/null +++ b/engine/src/test-data/Textures/Terrain/Pond/Pond.j3m @@ -0,0 +1,7 @@ +Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 8.0 + DiffuseMap: Repeat Textures/Terrain/Pond/Pond.png + NormalMap: Repeat Textures/Terrain/Pond/Pond_normal.png + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/Terrain/Pond/Pond.png b/engine/src/test-data/Textures/Terrain/Pond/Pond.png new file mode 100644 index 000000000..bd0794eb0 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/Pond/Pond.png differ diff --git a/engine/src/test-data/Textures/Terrain/Pond/Pond_normal.png b/engine/src/test-data/Textures/Terrain/Pond/Pond_normal.png new file mode 100644 index 000000000..081e39e67 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/Pond/Pond_normal.png differ diff --git a/engine/src/test-data/Textures/Terrain/Rock/Rock.PNG b/engine/src/test-data/Textures/Terrain/Rock/Rock.PNG new file mode 100644 index 000000000..4b5b1908b Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/Rock/Rock.PNG differ diff --git a/engine/src/test-data/Textures/Terrain/Rock/Rock.j3m b/engine/src/test-data/Textures/Terrain/Rock/Rock.j3m new file mode 100644 index 000000000..643653b48 --- /dev/null +++ b/engine/src/test-data/Textures/Terrain/Rock/Rock.j3m @@ -0,0 +1,7 @@ +Material Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 16.0 + DiffuseMap : Textures/Terrain/Rock/Rock.PNG + NormalMap : Textures/Terrain/Rock/Rock_normal.png + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/Terrain/Rock/Rock_normal.png b/engine/src/test-data/Textures/Terrain/Rock/Rock_normal.png new file mode 100644 index 000000000..48221c0c5 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/Rock/Rock_normal.png differ diff --git a/engine/src/test-data/Textures/Terrain/Rocky/Rocky.j3m b/engine/src/test-data/Textures/Terrain/Rocky/Rocky.j3m new file mode 100644 index 000000000..cc80d3af7 --- /dev/null +++ b/engine/src/test-data/Textures/Terrain/Rocky/Rocky.j3m @@ -0,0 +1,7 @@ +Material Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 100000.0 + DiffuseMap : Textures/Terrain/Rocky/RockyTexture.png + NormalMap : Textures/Terrain/Rocky/RockyNormals.png + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/Terrain/Rocky/RockyNormals.png b/engine/src/test-data/Textures/Terrain/Rocky/RockyNormals.png new file mode 100644 index 000000000..44cbb3a26 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/Rocky/RockyNormals.png differ diff --git a/engine/src/test-data/Textures/Terrain/Rocky/RockyTexture.png b/engine/src/test-data/Textures/Terrain/Rocky/RockyTexture.png new file mode 100644 index 000000000..a7cae6967 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/Rocky/RockyTexture.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/alphamap.png b/engine/src/test-data/Textures/Terrain/splat/alphamap.png new file mode 100644 index 000000000..98ee74a76 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/alphamap.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/alphamap2.png b/engine/src/test-data/Textures/Terrain/splat/alphamap2.png new file mode 100644 index 000000000..9367ef6b8 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/alphamap2.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/dirt.jpg b/engine/src/test-data/Textures/Terrain/splat/dirt.jpg new file mode 100644 index 000000000..474206828 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/dirt.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/splat/dirt_normal.png b/engine/src/test-data/Textures/Terrain/splat/dirt_normal.png new file mode 100644 index 000000000..2eb57f4af Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/dirt_normal.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/fortress512.png b/engine/src/test-data/Textures/Terrain/splat/fortress512.png new file mode 100644 index 000000000..b6b4ed165 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/fortress512.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/grass.jpg b/engine/src/test-data/Textures/Terrain/splat/grass.jpg new file mode 100644 index 000000000..8d5b97d3c Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/grass.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/splat/grass_normal.png b/engine/src/test-data/Textures/Terrain/splat/grass_normal.png new file mode 100644 index 000000000..9bde934c8 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/grass_normal.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/mountains1024.jpg b/engine/src/test-data/Textures/Terrain/splat/mountains1024.jpg new file mode 100644 index 000000000..37d647591 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/mountains1024.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/splat/mountains512.png b/engine/src/test-data/Textures/Terrain/splat/mountains512.png new file mode 100644 index 000000000..13cb4d11c Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/mountains512.png differ diff --git a/engine/src/test-data/Textures/Terrain/splat/road.jpg b/engine/src/test-data/Textures/Terrain/splat/road.jpg new file mode 100644 index 000000000..21195e6eb Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/road.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/splat/road_normal.png b/engine/src/test-data/Textures/Terrain/splat/road_normal.png new file mode 100644 index 000000000..ae56480a3 Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/splat/road_normal.png differ diff --git a/engine/src/test-data/profiling points b/engine/src/test-data/profiling points new file mode 100644 index 000000000..168346ce1 --- /dev/null +++ b/engine/src/test-data/profiling points @@ -0,0 +1,32 @@ +CPU usage: +---------- + +ObjLoader.load() + - ObjLoader.readLine() + +HDRLoader.writeRGBE() // need faster RGBE8 -> RGB16F conversion + +// OpenGL resource-intesive points +Renderer.renderQueue() +Renderer.setVertexAttrib() +Material.apply() + +Memory usage: +------------- + - OBJLoader +Java's Scanner class allocates approx. 8 MB of memory +to load the teapot model. Either implement ObjLoader without Scanner +or create an import/export system! + + - AWTLoader +Using AWT for loading images is slow and uses more memory +than a home-grown loader. Use DDS and TGA formats more. + + - Shader.getUniforms +This method generates a collection to represent the Uniforms +in the shader and is used by Renderer.updateShaderUniforms() +Need a faster method to iterate & update uniforms in a shader. + + - Material.apply +Same thing as above. Generates a Collection and then an Iterator for a HashMap. +First, consider if using a HashMap is neccessary.. \ No newline at end of file diff --git a/engine/src/test/jme3test/TestChooser.java b/engine/src/test/jme3test/TestChooser.java new file mode 100644 index 000000000..20fa07151 --- /dev/null +++ b/engine/src/test/jme3test/TestChooser.java @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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 java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.HeadlessException; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +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.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListModel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +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 Class selectedClass = null; + + /** + * Constructs a new TestChooser that is initially invisible. + */ + public TestChooser() throws HeadlessException { + super((JFrame) null, "TestChooser", true); + } + + /** + * @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 find(String pckgname, boolean recursive, + Vector 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; + } + } + 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 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 Class appClass){ + if (appClass == null){ + JOptionPane.showMessageDialog(rootPane, + "Please select a test from the list", + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + + final Object app; + try { + app = appClass.newInstance(); + final Method mainMethod = appClass.getMethod("main", (new String[0]).getClass()); + new Thread(new Runnable(){ + public void run(){ + try { + mainMethod.invoke(app, new Object[]{new String[0]}); + } catch (IllegalAccessException ex) { + logger.log(Level.SEVERE, "Cannot access constructor: "+appClass.getName(), ex); + } catch (IllegalArgumentException ex) { + logger.log(Level.SEVERE, "main() had illegal argument: "+appClass.getName(), ex); + } catch (InvocationTargetException ex) { + logger.log(Level.SEVERE, "main() method had exception: "+appClass.getName(), ex); + } + } + }).start(); + } catch (InstantiationException ex) { + logger.log(Level.SEVERE, "Failed to create app: "+appClass.getName(), ex); + } catch (IllegalAccessException ex) { + logger.log(Level.SEVERE, "Cannot access constructor: "+appClass.getName(), ex); + } catch (NoSuchMethodException ex){ + logger.log(Level.SEVERE, "Test class doesn't have main method: "+appClass.getName(), ex); + } + } + + /** + * Code to create components and action listeners. + * + * @param classes + * what Classes to show in the list box + */ + private void setup(Vector 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(); + 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 = (Class) list.getSelectedValue(); + } + }); + + 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); + dispose(); + } + }); + + 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); + 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) { + } + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + new TestChooser().start(args); + } + }); + } + + protected void start(String[] args) { + final Vector classes = new Vector(); + logger.info("Composing Test list..."); + addDisplayedClasses(classes); + setup(classes); + Class cls; + setVisible(true); + } + + protected void addDisplayedClasses(Vector 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) { + if (classes.getModel().getSize() == 1) { + selectedClass = (Class) classes.getModel().getElementAt(0); + TestChooser.this.dispose(); + } + } + }); + + search.add(jtf); + 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 000000000..d4def9493 --- /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.elements.render.TextRenderer; + +/** + * + * @author Nehon + */ +public class SubtitleTrack extends GuiTrack{ + private String text=""; + + public SubtitleTrack(String screen,float initialDuration, String text) { + super(screen, initialDuration); + this.text=text; + } + + @Override + public void onPlay() { + super.onPlay(); + //REMY FIX THIS + //nifty.getScreen(screen).findElementByName("text").getRenderer(TextRenderer.class).changeText(text); + } + + + + + + + + +} diff --git a/engine/src/test/jme3test/animation/TestCameraMotionPath.java b/engine/src/test/jme3test/animation/TestCameraMotionPath.java new file mode 100644 index 000000000..8af789edd --- /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.cinematic.events.MotionTrack; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.MotionPathListener; +import com.jme3.app.SimpleApplication; +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.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(cam); + camNode.setControlDir(ControlDirection.SpatialToCamera); + camNode.getControl(0).setEnabled(false); + camNode.setName("Motion 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); + 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.getPathInterpolation() == MotionPath.PathInterpolation.CatmullRom) { + path.setPathInterpolation(MotionPath.PathInterpolation.Linear); + } else { + path.setPathInterpolation(MotionPath.PathInterpolation.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 000000000..80b81a5e5 --- /dev/null +++ b/engine/src/test/jme3test/animation/TestCinematic.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.cinematic.Cinematic; +import com.jme3.animation.LoopMode; +import com.jme3.cinematic.events.CinematicEvent; +import com.jme3.cinematic.events.MotionTrack; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.events.SoundTrack; +import com.jme3.app.SimpleApplication; +import com.jme3.cinematic.events.AbstractCinematicEvent; +import com.jme3.cinematic.events.AnimationTrack; +import com.jme3.cinematic.PlayState; +import com.jme3.cinematic.events.CinematicEventListener; +import com.jme3.cinematic.events.PositionTrack; +import com.jme3.cinematic.events.RotationTrack; +import com.jme3.cinematic.events.ScaleTrack; +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.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.FadeFilter; +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; + +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; + + public static void main(String[] args) { + TestCinematic app = new TestCinematic(); + app.start(); + + } + + @Override + public void simpleInitApp() { + //just some text + 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); + cinematic.bindUi("jme3test/animation/subtitle.xml"); + stateManager.attach(cinematic); + + createCameraMotion(); + + + cinematic.addCinematicEvent(0, new AbstractCinematicEvent() { + + @Override + public void onPlay() { + fade.setValue(0); + fade.fadeIn(); + } + + @Override + public void onUpdate(float tpf) { + } + + @Override + public void onStop() { + } + + @Override + public void onPause() { + } + }); + cinematic.addCinematicEvent(0, new PositionTrack(teapot, new Vector3f(10, 0, 10), 0)); + cinematic.addCinematicEvent(0, new ScaleTrack(teapot, new Vector3f(1, 1, 1), 0)); + float[] rotation = {0, 0, 0}; + cinematic.addCinematicEvent(0, new RotationTrack(teapot, rotation, 0)); + + cinematic.addCinematicEvent(0, new PositionTrack(teapot, new Vector3f(10, 0, -10), 20)); + cinematic.addCinematicEvent(0, new ScaleTrack(teapot, new Vector3f(4, 4, 4), 10)); + cinematic.addCinematicEvent(10, new ScaleTrack(teapot, new Vector3f(1, 1, 1), 10)); + float[] rotation2 = {0, FastMath.TWO_PI, 0}; + cinematic.addCinematicEvent(0, new RotationTrack(teapot, rotation2, 20)); + + cinematic.activateCamera(0, "aroundCam"); + cinematic.addCinematicEvent(0, cameraMotionTrack); + cinematic.addCinematicEvent(0, new SoundTrack("Sound/Environment/Nature.ogg", LoopMode.Loop)); + cinematic.addCinematicEvent(3, new SoundTrack("Sound/Effects/kick.wav")); + cinematic.addCinematicEvent(3, new SubtitleTrack("start", 3, "jMonkey engine really kicks A...")); + cinematic.addCinematicEvent(5.0f, new SoundTrack("Sound/Effects/Beep.ogg", 1)); + cinematic.addCinematicEvent(6, new AnimationTrack(model, "Walk", LoopMode.Loop)); + cinematic.activateCamera(6, "topView"); + cinematic.activateCamera(10, "aroundCam"); + + cinematic.addCinematicEvent(19, new AbstractCinematicEvent() { + + @Override + public void onPlay() { + 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) { + chaseCam.setEnabled(true); + System.out.println("pause"); + } + + public void onStop(CinematicEvent cinematic) { + chaseCam.setEnabled(true); + fade.setValue(1); + System.out.println("stop"); + } + }); + + 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(model.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); + + PssmShadowRenderer pssm = new PssmShadowRenderer(assetManager, 512, 1); + pssm.setDirection(new Vector3f(0, -1, -1).normalizeLocal()); + pssm.setShadowIntensity(0.4f); + viewPort.addProcessor(pssm); + + fpp = new FilterPostProcessor(assetManager); + //fpp.setNumSamples(4); + fade = new FadeFilter(); + fpp.addFilter(fade); + viewPort.addProcessor(fpp); + + } + + private void initInputs() { + inputManager.addMapping("togglePause", new KeyTrigger(keyInput.KEY_RETURN)); + + 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(); + } else { + cinematic.play(); + } + } + + } + }; + + inputManager.addListener(acl, "togglePause"); + + } +} diff --git a/engine/src/test/jme3test/animation/TestMotionPath.java b/engine/src/test/jme3test/animation/TestMotionPath.java new file mode 100644 index 000000000..2aedc2369 --- /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.cinematic.events.MotionTrack; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.MotionPathListener; +import com.jme3.app.SimpleApplication; +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.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Line; + +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(0.1f); + + 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.getPathInterpolation() == MotionPath.PathInterpolation.CatmullRom) { + path.setPathInterpolation(MotionPath.PathInterpolation.Linear); + } else { + path.setPathInterpolation(MotionPath.PathInterpolation.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/subtitle.xml b/engine/src/test/jme3test/animation/subtitle.xml new file mode 100644 index 000000000..91b09f245 --- /dev/null +++ b/engine/src/test/jme3test/animation/subtitle.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/engine/src/test/jme3test/app/TestApplication.java b/engine/src/test/jme3test/app/TestApplication.java new file mode 100644 index 000000000..734cb4102 --- /dev/null +++ b/engine/src/test/jme3test/app/TestApplication.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.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(); + + System.out.println("Creating JOGL application.."); + settings = new AppSettings(true); + settings.setRenderer(AppSettings.JOGL); + app = new Application(); + app.setSettings(settings); + app.start(); + Thread.sleep(5000); + 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 000000000..5635b3604 --- /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); + } + + @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 000000000..dffee9a40 --- /dev/null +++ b/engine/src/test/jme3test/app/TestChangeAppIcon.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.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; + +/** + * Test Application functionality, such as create, restart, destroy, etc. + * @author Kirill + */ +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 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 000000000..b83f33624 --- /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 000000000..03ed4b2b8 --- /dev/null +++ b/engine/src/test/jme3test/app/TestIDList.java @@ -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. + */ + +package jme3test.app; + +import com.jme3.renderer.IDList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +public class TestIDList { + + static class StateCol { + + static Random rand = new Random(); + + Map objs = new HashMap(); + + public StateCol(){ + // populate with free ids + List 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 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 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/TestTempVars.java b/engine/src/test/jme3test/app/TestTempVars.java new file mode 100644 index 000000000..cd175931c --- /dev/null +++ b/engine/src/test/jme3test/app/TestTempVars.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.app; + +import com.jme3.util.TempVars; + +public class TestTempVars { + + public static void methodThatUsesTempVars(){ + TempVars vars = TempVars.get(); + + assert vars.lock(); + { + vars.vect1.set(123,999,-55); + } + assert vars.unlock(); + } + + public static void main(String[] args){ + System.err.println("NOTE: This test simulates a data corruption attempt\n" + + " in the engine. If assertions are enabled (-ea VM flag), the \n" + + "data corruption will be detected and displayed below."); + + TempVars vars = TempVars.get(); + + assert vars.lock(); + { + // do something with temporary vars + vars.vect1.addLocal(vars.vect2); + } + assert vars.unlock(); + + + + assert vars.lock(); + { + // set a value + vars.vect1.set(1,1,1); + + // method that currupts the value + methodThatUsesTempVars(); + + // read the value + System.out.println(vars.vect1); + } + assert vars.unlock(); + } + +} 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 000000000..fddb63a1b --- /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 000000000..53124c00b --- /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("jme3test/niftygui/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); + } + + @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 000000000..c56d9e286 --- /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(), "png"); + 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.png"); + + 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.png"); + + 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 000000000..e68cca302 --- /dev/null +++ b/engine/src/test/jme3test/asset/TestAssetCache.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 jme3test.asset; + +import com.jme3.asset.AssetCache; +import com.jme3.asset.AssetKey; + +public class TestAssetCache { + + private static class MyAsset { + + private String name; + private byte[] bytes = new byte[100]; + + public MyAsset(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } + +// private static final long memoryUsage(){ +// return Runtime.getRuntime(). +// } +// + public static void main(String[] args){ + AssetCache cache = new AssetCache(); + + System.gc(); + System.gc(); + System.gc(); + System.gc(); + + long startMem = Runtime.getRuntime().freeMemory(); + + for (int i = 0; i < 10000; i++){ + MyAsset asset = new MyAsset("asset"+i); + AssetKey key = new AssetKey(asset.getName()); + } + + long endMem = Runtime.getRuntime().freeMemory(); + System.out.println("No cache diff:\t"+(startMem-endMem)); + + System.gc(); + System.gc(); + System.gc(); + System.gc(); + + endMem = Runtime.getRuntime().freeMemory(); + System.out.println("No cache gc diff:\t"+(startMem-endMem)); + startMem = endMem; + + for (int i = 0; i < 10000; i++){ + MyAsset asset = new MyAsset("asset"+i); + AssetKey key = new AssetKey(asset.getName()); + cache.addToCache(key, asset); + } + + endMem = Runtime.getRuntime().freeMemory(); + System.out.println("Cache diff:\t"+(startMem-endMem)); + + System.gc(); + System.gc(); + System.gc(); + System.gc(); + + endMem = Runtime.getRuntime().freeMemory(); + System.out.println("Cache gc diff:\t"+(startMem-endMem)); +// System.out.println("Estimated usage: "+) + } + +} diff --git a/engine/src/test/jme3test/asset/TestManyLocators.java b/engine/src/test/jme3test/asset/TestManyLocators.java new file mode 100644 index 000000000..c91eb11ab --- /dev/null +++ b/engine/src/test/jme3test/asset/TestManyLocators.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.asset; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.ModelKey; +import com.jme3.asset.TextureKey; +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("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 000000000..712297039 --- /dev/null +++ b/engine/src/test/jme3test/asset/TestOnlineJar.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.asset; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.material.Material; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; +import com.jme3.texture.plugins.AWTLoader; + +/** + * 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 000000000..837ff33fe --- /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.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.material.Material; +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/AudioApp.java b/engine/src/test/jme3test/audio/AudioApp.java new file mode 100644 index 000000000..2770e25c3 --- /dev/null +++ b/engine/src/test/jme3test/audio/AudioApp.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 jme3test.audio; + +import com.jme3.audio.AudioRenderer; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.audio.Listener; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; + +public class AudioApp { + + private static final float UPDATE_RATE = 0.01f; + + protected AssetManager manager; + protected Listener listener; + protected AudioRenderer ar; + + public AudioApp(){ + AppSettings settings = new AppSettings(true); + settings.setRenderer(null); // force dummy renderer (?) + settings.setAudioRenderer(AppSettings.LWJGL_OPENAL); + ar = JmeSystem.newAudioRenderer(settings); + ar.initialize(); + manager = new DesktopAssetManager(true); + + listener = new Listener(); + ar.setListener(listener); + } + + public void initAudioApp(){ + } + + public void updateAudioApp(float tpf){ + } + + public void start(){ + initAudioApp(); + + while (true){ + updateAudioApp(UPDATE_RATE); + ar.update(UPDATE_RATE); + + try{ + Thread.sleep((int) (UPDATE_RATE * 1000f)); + }catch (InterruptedException ex){ + ex.printStackTrace(); + } + } + } +} diff --git a/engine/src/test/jme3test/audio/TestAmbient.java b/engine/src/test/jme3test/audio/TestAmbient.java new file mode 100644 index 000000000..79fd160be --- /dev/null +++ b/engine/src/test/jme3test/audio/TestAmbient.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 jme3test.audio; + +import com.jme3.audio.AudioNode; +//import com.jme3.audio.PointAudioSource; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; + +public class TestAmbient extends AudioApp { + + private AudioNode river, nature, waves; +// 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 initAudioApp(){ + waves = new AudioNode(manager, "Sound/Environment/Ocean Waves.ogg", false); + waves.setPositional(true); + + nature = new AudioNode(manager, "Sound/Environment/Nature.ogg", true); +// river = new AudioSource(manager, "sounds/river.ogg"); + +// 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); +// ar.setEnvironment(env); + + waves.setLocalTranslation(new Vector3f(4, -1, 30)); + waves.setMaxDistance(5); + waves.setRefDistance(1); + + nature.setVolume(3); + ar.playSourceInstance(waves); + ar.playSource(nature); + } + + @Override + public void updateAudioApp(float tpf){ + time += tpf; + + if (time > nextTime){ + + time = 0; + nextTime = FastMath.nextRandomFloat() * 2 + 0.5f; + } + } + +} diff --git a/engine/src/test/jme3test/audio/TestDoppler.java b/engine/src/test/jme3test/audio/TestDoppler.java new file mode 100644 index 000000000..fd9cff3fe --- /dev/null +++ b/engine/src/test/jme3test/audio/TestDoppler.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.audio.AudioNode; +import com.jme3.math.Vector3f; + +/** + * Test Doppler Effect + */ +public class TestDoppler extends AudioApp { + + private AudioNode ufo; + + private float location = 0; + private float rate = 1; + + public static void main(String[] args){ + TestDoppler test = new TestDoppler(); + test.start(); + } + + @Override + public void initAudioApp(){ + ufo = new AudioNode(manager, "Sound/Effects/Beep.ogg", false); + ufo.setPositional(true); + ufo.setLooping(true); + ar.playSource(ufo); + } + + @Override + public void updateAudioApp(float tpf){ + // move the location variable left and right + if (location > 10){ + location = 10; + rate = -rate; + ufo.setVelocity(new Vector3f(rate*10, 0, 0)); + }else if (location < -10){ + location = -10; + rate = -rate; + ufo.setVelocity(new Vector3f(rate*10, 0, 0)); + }else{ + location += rate * tpf * 10; + } + ufo.setLocalTranslation(location, 0, 2); + ufo.updateGeometricState(); + } + +} diff --git a/engine/src/test/jme3test/audio/TestMusicPlayer.form b/engine/src/test/jme3test/audio/TestMusicPlayer.form new file mode 100644 index 000000000..dced8a41f --- /dev/null +++ b/engine/src/test/jme3test/audio/TestMusicPlayer.form @@ -0,0 +1,117 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/src/test/jme3test/audio/TestMusicPlayer.java b/engine/src/test/jme3test/audio/TestMusicPlayer.java new file mode 100644 index 000000000..6374b2f50 --- /dev/null +++ b/engine/src/test/jme3test/audio/TestMusicPlayer.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.audio.AudioKey; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioNode.Status; +import com.jme3.audio.Listener; +import com.jme3.audio.QueuedAudioRenderer; +import com.jme3.audio.plugins.OGGLoader; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +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); // force dummy renderer (?) + settings.setAudioRenderer("LWJGL"); + ar = JmeSystem.newAudioRenderer(settings); + ar.initialize(); + ar.setListener(listener); + } + + /** 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") + // //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(); + }// //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(); + OGGLoader loader = new OGGLoader(); + AudioKey key = new AudioKey(selected.getName(), true); + try{ + musicData = (AudioData) loader.load(new AssetInfo(null, key) { + @Override + public InputStream openStream() { + try{ + return new FileInputStream(selected); + }catch (FileNotFoundException ex){ + } + return null; + } + }); + }catch (IOException ex){ + } + + 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); + ar.playSource(musicSource); + } + }//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.setGain( (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){ + ar.stopSource(musicSource); + ar.playSource(musicSource); + } + 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/TestOgg.java b/engine/src/test/jme3test/audio/TestOgg.java new file mode 100644 index 000000000..273b83fdc --- /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.asset.plugins.UrlLocator; +import com.jme3.audio.AudioNode; +import com.jme3.audio.LowPassFilter; + +public class TestOgg extends AudioApp { + + private AudioNode src; + + public static void main(String[] args){ + TestOgg test = new TestOgg(); + test.start(); + } + + @Override + public void initAudioApp(){ + System.out.println("Playing without filter"); + src = new AudioNode(manager, "Sound/Effects/Foot steps.ogg", true); + ar.playSource(src); + } + + @Override + public void updateAudioApp(float tpf){ + if (src.getStatus() != AudioNode.Status.Playing){ + ar.deleteAudioData(src.getAudioData()); + + System.out.println("Playing with low pass filter"); + src = new AudioNode(manager, "Sound/Effects/Foot steps.ogg", true); + src.setDryFilter(new LowPassFilter(1f, .1f)); + src.setVolume(3); + ar.playSource(src); + } + } + +} diff --git a/engine/src/test/jme3test/audio/TestReverb.java b/engine/src/test/jme3test/audio/TestReverb.java new file mode 100644 index 000000000..53fa9543e --- /dev/null +++ b/engine/src/test/jme3test/audio/TestReverb.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.audio; + +import com.jme3.audio.AudioNode; +import com.jme3.audio.Environment; +//import com.jme3.audio.PointAudioSource; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; + +public class TestReverb extends AudioApp { + + private AudioNode src; + private float time = 0; + private float nextTime = 1; + + public static void main(String[] args){ + TestReverb test = new TestReverb(); + test.start(); + } + + @Override + public void initAudioApp(){ + src = new AudioNode(manager, "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 } + ; + +// ar.setEnvironment(new Environment(eax)); + Environment env = Environment.Cavern; + ar.setEnvironment(env); + } + + @Override + public void updateAudioApp(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); + + src.setLocalTranslation(v); + ar.playSourceInstance(src); + 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 000000000..f6710b00e --- /dev/null +++ b/engine/src/test/jme3test/audio/TestWav.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.audio; + +import com.jme3.audio.AudioNode; + +public class TestWav extends AudioApp { + + private float time = 0; + private AudioNode src; + + public static void main(String[] args){ + TestWav test = new TestWav(); + test.start(); + } + + public static void sleep(float time){ + try{ + Thread.sleep((long) (time * 1000)); + }catch (InterruptedException ex){ + } + } + + @Override + public void updateAudioApp(float tpf){ + time += tpf; + if (time > .1f){ + ar.playSourceInstance(src); + time = 0; + } + + } + + @Override + public void initAudioApp(){ + src = new AudioNode(manager, "Sound/Effects/Gun.wav", false); + src.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 000000000..4a30d782f --- /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 clazz = (Class) 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 000000000..5606e74de --- /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 clazz = (Class) 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(){ + 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/TestCanvas.java b/engine/src/test/jme3test/awt/TestCanvas.java new file mode 100644 index 000000000..b8f8392eb --- /dev/null +++ b/engine/src/test/jme3test/awt/TestCanvas.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.awt.Canvas; +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 javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + +public class TestCanvas { + + private static JmeCanvasContext context; + private static Canvas canvas; + private static Application app; + private static JFrame frame; + private static final String appClass = "jme3test.model.shape.TestBox"; + + 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(); + } + }); + + JMenuBar menuBar = new JMenuBar(); + frame.setJMenuBar(menuBar); + + JMenu menuFile = new JMenu("File"); + menuBar.add(menuFile); + + final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas"); + menuFile.add(itemRemoveCanvas); + itemRemoveCanvas.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (itemRemoveCanvas.getText().equals("Remove Canvas")){ + frame.getContentPane().remove(canvas); + + // force OS to repaint over canvas .. + // this is needed since AWT does not handle + // that when a heavy-weight component is removed. + frame.setVisible(false); + frame.setVisible(true); + frame.requestFocus(); + + itemRemoveCanvas.setText("Add Canvas"); + }else if (itemRemoveCanvas.getText().equals("Add Canvas")){ + frame.getContentPane().add(canvas); + itemRemoveCanvas.setText("Remove Canvas"); + } + } + }); + + JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas"); + menuFile.add(itemKillCanvas); + itemKillCanvas.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + frame.getContentPane().remove(canvas); + app.stop(true); + + createCanvas(appClass); + frame.getContentPane().add(canvas); + frame.pack(); + startApp(); + } + }); + + JMenuItem itemExit = new JMenuItem("Exit"); + menuFile.add(itemExit); + itemExit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + frame.dispose(); + app.stop(); + } + }); + + JMenu menuEdit = new JMenu("Edit"); + menuBar.add(menuEdit); + JMenuItem itemDelete = new JMenuItem("Delete"); + menuEdit.add(itemDelete); + + JMenu menuView = new JMenu("View"); + menuBar.add(menuView); + JMenuItem itemSetting = new JMenuItem("Settings"); + menuView.add(itemSetting); + + JMenu menuHelp = new JMenu("Help"); + menuBar.add(menuHelp); + } + + public static void createCanvas(String appClass){ + AppSettings settings = new AppSettings(true); + settings.setWidth( Math.max(640, frame.getContentPane().getWidth()) ); + settings.setHeight( Math.max(480, frame.getContentPane().getHeight()) ); + + try{ + Class clazz = (Class) 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(); + + context = (JmeCanvasContext) app.getContext(); + canvas = context.getCanvas(); + canvas.setSize(settings.getWidth(), settings.getHeight()); + } + + public static void startApp(){ + app.startCanvas(); + app.enqueue(new Callable(){ + public Void call(){ + if (app instanceof SimpleApplication){ + SimpleApplication simpleApp = (SimpleApplication) app; + simpleApp.getFlyByCamera().setDragToRotate(true); + } + return null; + } + }); + + } + + public static void main(String[] args){ + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + + createFrame(); + createCanvas(appClass); + + frame.getContentPane().add(canvas); + frame.pack(); + startApp(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + }); + } + +} diff --git a/engine/src/test/jme3test/bounding/TestRayCollision.java b/engine/src/test/jme3test/bounding/TestRayCollision.java new file mode 100644 index 000000000..922c2edfa --- /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 000000000..63b6b64d8 --- /dev/null +++ b/engine/src/test/jme3test/bullet/BombControl.java @@ -0,0 +1,170 @@ +/* + * 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.EmitterSphereShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +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 curTime = -1.0f; + + 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(-5f); + 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); + } + + 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 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; + 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 && 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(); + } + + @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 000000000..3d85b16c4 --- /dev/null +++ b/engine/src/test/jme3test/bullet/PhysicsHoverControl.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.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; + +/** + * 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 child + * @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 000000000..447544db8 --- /dev/null +++ b/engine/src/test/jme3test/bullet/PhysicsTestHelper.java @@ -0,0 +1,162 @@ +/* + * 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/SimpleTextured.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(2, -4, 2); + sphereGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0)); + rootNode.attachChild(sphereGeometry); + space.add(sphereGeometry); + + } + + /** + * creates a box geometry with a RigidBodyControl + * @param assetManager + * @return + */ + public static Geometry createPhysicsTestBox(AssetManager assetManager) { + Material material = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.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/SimpleTextured.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/SimpleTextured.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 000000000..b50e55845 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestAttachDriver.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 jme3test.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.ColorRGBA; +import com.jme3.math.FastMath; +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.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/WireColor.j3md"); + 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 000000000..7cfab75b8 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestAttachGhostObject.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.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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.material.Material; +import com.jme3.math.ColorRGBA; +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() { + + Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Gray); + + 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 000000000..cb33148d6 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestBoneRagdoll.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.bullet; + +import com.jme3.animation.AnimControl; +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RagdollControl; +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; + +/** + * PHYSICS RAGDOLLS ARE NOT WORKING PROPERLY YET! + * @author normenhansen + */ +public class TestBoneRagdoll extends SimpleApplication { + + private BulletAppState bulletAppState; + + public static void main(String[] args){ + TestBoneRagdoll app = new TestBoneRagdoll(); + app.start(); + } + + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + setupLight(); + + Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + + //debug view + AnimControl control= model.getControl(AnimControl.class); + SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton()); + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + mat2.setColor("Color", ColorRGBA.Green); + mat2.getAdditionalRenderState().setDepthTest(false); + skeletonDebug.setMaterial(mat2); + + //Note: PhysicsRagdollControl is still TODO, constructor will change + RagdollControl ragdoll = new RagdollControl(); + + model.addControl(ragdoll); + getPhysicsSpace().add(ragdoll); + speed = .2f; + + rootNode.attachChild(model); + rootNode.attachChild(skeletonDebug); + } + + 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(); + } + +} diff --git a/engine/src/test/jme3test/bullet/TestBrickWall.java b/engine/src/test/jme3test/bullet/TestBrickWall.java new file mode 100644 index 000000000..a0d85ca88 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestBrickWall.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.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.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 final Sphere bullet; + private static final Box brick; + private static final SphereCollisionShape bulletCollisionShape; + + static { + 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)); + } + 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); + 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"); + + 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()); + 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 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/SimpleTextured.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/SimpleTextured.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/SimpleTextured.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.png"); + 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 000000000..f0af517d7 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestCcd.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 jme3test.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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 static final Sphere bullet; + private static final SphereCollisionShape bulletCollisionShape; + private BulletAppState bulletAppState; + + static { + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.1f); + } + + 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); + setupKeys(); + + mat = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Green); + + mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + 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 000000000..92caaddd6 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestCollisionGroups.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.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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.material.Material; +import com.jme3.math.ColorRGBA; +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); + Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Red); + Material mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat2.setColor("Color", ColorRGBA.Magenta); + + // 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 000000000..c134976ed --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestCollisionListener.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.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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.material.Material; +import com.jme3.math.ColorRGBA; +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 Material mat; + private static final Sphere bullet; + private static final SphereCollisionShape bulletCollisionShape; + + static { + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + } + + 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); + + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); + + mat = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Red); + + // 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/TestFancyCar.java b/engine/src/test/jme3test/bullet/TestFancyCar.java new file mode 100644 index 000000000..6a4a8967e --- /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.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +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 000000000..06ae522ca --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestGhostObject.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.bullet; + +import com.jme3.app.Application; +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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.material.Material; +import com.jme3.math.ColorRGBA; +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); + Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Red); + 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 000000000..60336a543 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestHoveringTank.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 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.ColorRGBA; +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.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.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; +import jme3tools.converters.ImageToAwt; + +public class TestHoveringTank extends SimpleApplication implements AnalogListener, + ActionListener { + + private BulletAppState bulletAppState; + private PhysicsHoverControl hoverControl; + private Spatial spaceCraft; + TerrainQuad terrain; + Material matRock; + Material matWire; + 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.png"); + 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); + + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(2, 2, 2)); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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/TestLocalPhysics.java b/engine/src/test/jme3test/bullet/TestLocalPhysics.java new file mode 100644 index 000000000..849d02666 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestLocalPhysics.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.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.math.Vector3f; +import com.jme3.scene.shape.Sphere; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +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.scene.Node; + +/** + * 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 000000000..ff93132c3 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestPhysicsCar.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 jme3test.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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/WireColor.j3md"); + 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 000000000..c7c0fc5f8 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestPhysicsCharacter.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.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +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.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; + +/** + * + * @author normenhansen + */ +public class TestPhysicsCharacter extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private CharacterControl physicsCharacter; + private Vector3f walkDirection = new Vector3f(); + private Material mat; + private static final Sphere bullet; + private static final SphereCollisionShape bulletCollisionShape; + + static { + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + } + + public static void main(String[] args) { + TestPhysicsCharacter app = new TestPhysicsCharacter(); + 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("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "shoot"); + inputManager.addListener(this, "Lefts"); + inputManager.addListener(this, "Rights"); + inputManager.addListener(this, "Ups"); + inputManager.addListener(this, "Downs"); + inputManager.addListener(this, "Space"); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsTestHelper.createPhysicsTestWorld(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(3, 6, 0)); + + Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model.scale(0.25f); + model.addControl(physicsCharacter); + getPhysicsSpace().add(physicsCharacter); + rootNode.attachChild(model); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + physicsCharacter.setWalkDirection(walkDirection); + physicsCharacter.setViewDirection(walkDirection); + cam.lookAt(physicsCharacter.getPhysicsLocation(), Vector3f.UNIT_Y); + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Lefts")) { + if (value) { + walkDirection.addLocal(new Vector3f(-.1f, 0, 0)); + } else { + walkDirection.addLocal(new Vector3f(.1f, 0, 0)); + } + } else if (binding.equals("Rights")) { + if (value) { + walkDirection.addLocal(new Vector3f(.1f, 0, 0)); + } else { + walkDirection.addLocal(new Vector3f(-.1f, 0, 0)); + } + } else if (binding.equals("Ups")) { + if (value) { + walkDirection.addLocal(new Vector3f(0, 0, -.1f)); + } else { + walkDirection.addLocal(new Vector3f(0, 0, .1f)); + } + } else if (binding.equals("Downs")) { + if (value) { + walkDirection.addLocal(new Vector3f(0, 0, .1f)); + } else { + walkDirection.addLocal(new Vector3f(0, 0, -.1f)); + } + } else if (binding.equals("Space")) { + physicsCharacter.jump(); + } + } +} diff --git a/engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.java b/engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.java new file mode 100644 index 000000000..e0ffd5b25 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.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 jme3test.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +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.material.Material; +import com.jme3.math.ColorRGBA; +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() { + + Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/WireColor.j3md"); + mat.setColor("Color", ColorRGBA.Yellow); + + 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 000000000..5c3299a9b --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestPhysicsReadWrite.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.bullet; + + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.shape.Sphere; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +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.bullet.joints.HingeJoint; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.math.Plane; +import com.jme3.scene.Node; +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 000000000..dbd6ce7a5 --- /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.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +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 000000000..9287f10ae --- /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/TestSimplePhysics.java b/engine/src/test/jme3test/bullet/TestSimplePhysics.java new file mode 100644 index 000000000..96581f37e --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestSimplePhysics.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 jme3test.bullet; + +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.math.Vector3f; +import com.jme3.scene.shape.Sphere; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +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.scene.Node; + +/** + * 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 000000000..b309fbcc6 --- /dev/null +++ b/engine/src/test/jme3test/bullet/TestWalkingChar.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.bullet; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimEventListener; +import com.jme3.animation.LoopMode; +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +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.EmitterSphereShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +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.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; +import jme3tools.converters.ImageToAwt; + +/** + * + * @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 matWire; + 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(matRock); + 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/SolidColor.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(-5f); + 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.png"); + 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); + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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(1.5f, 2f); + 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, 10, -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.2f); + Vector3f camLeft = cam.getLeft().clone().multLocal(0.2f); + 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(2))); + 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 000000000..7e02ad1b6 --- /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/SphereMotionAllowedListener.java b/engine/src/test/jme3test/collision/SphereMotionAllowedListener.java new file mode 100644 index 000000000..a77a88827 --- /dev/null +++ b/engine/src/test/jme3test/collision/SphereMotionAllowedListener.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.MotionAllowedListener; +import com.jme3.collision.SweepSphere; +import com.jme3.math.Plane; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +public class SphereMotionAllowedListener implements MotionAllowedListener { + + private Ray ray = new Ray(); + private SweepSphere ss = new SweepSphere(); + private CollisionResults results = new CollisionResults(); + private Spatial scene; + private Vector3f dimension = new Vector3f(); + + private Vector3f newPos = new Vector3f(); + private Vector3f newVel = new Vector3f(); + + private float charHeight; + private float footHeight; + private float footStart; + private float sphHeight; + private float sphCenter; + + final float unitsPerMeter = 100.0f; + final float unitScale = unitsPerMeter / 100.0f; + final float veryCloseDist = 0.005f * unitScale; + + private int depth = 0; + + public SphereMotionAllowedListener(Spatial scene, Vector3f dimension){ + if (scene == null || dimension == null) + throw new NullPointerException(); + + this.scene = scene; + + charHeight = dimension.getY(); + + footHeight = charHeight / 3f; + footStart = -(charHeight / 2f) + footHeight; + sphHeight = charHeight - footHeight; + sphCenter = (charHeight / 2f) - (sphHeight / 2f); + this.dimension.set(dimension); + this.dimension.setY(sphHeight); + } + + private void collideWithWorld(){ + if (depth > 3){ +// System.out.println("DEPTH LIMIT REACHED!!"); + return; + } + + if (newVel.length() < veryCloseDist) + return; + + Vector3f destination = newPos.add(0, sphCenter, 0).add(newVel); + + ss.setCenter(newPos.add(0, sphCenter, 0)); + ss.setVelocity(newVel); + ss.setDimension(dimension); + + results.clear(); + scene.collideWith(ss, results); + + if (results.size() == 0){ + newPos.addLocal(newVel); + return; + } + + for (int i = 0; i < results.size(); i++){ + CollisionResult collision = results.getCollision(i); + // *** collision occured *** +// Vector3f destination = newPos.add(newVel); + Vector3f contactPoint = collision.getContactPoint().clone(); + float dist = collision.getDistance(); + + if (dist >= veryCloseDist){ + // P += ||V|| * dist + Vector3f tmp = new Vector3f(newVel); + tmp.normalizeLocal().multLocal(dist - veryCloseDist); + newPos.addLocal(tmp); + + tmp.normalizeLocal(); + tmp.multLocal(veryCloseDist); + contactPoint.subtractLocal(tmp); + } + + // Vector3f normal = newPos.subtract(contactPoint).normalizeLocal(); + Vector3f normal = collision.getContactNormal(); + + Plane p = new Plane(); + p.setOriginNormal(contactPoint, normal); + + Vector3f destinationOnPlane = p.getClosestPoint(destination); + newVel.set(destinationOnPlane).subtractLocal(contactPoint); + // normal.multLocal(normal.dot(destination) - veryCloseDist); + //// normal.multLocal(p.pseudoDistance(destination)); + // Vector3f newDest = destination.add(normal); + // newVel.set(newDest).subtractLocal(contactPoint); + + // recurse: + if (newVel.length() < veryCloseDist){ + return; + } + } + + depth = depth + 1; + collideWithWorld(); + } + + public void checkMotionAllowed(Vector3f position, Vector3f velocity) { + if (velocity.getX() == 0 && velocity.getZ() == 0) + return; + + depth = 0; + newPos.set(position); + newVel.set(velocity); + velocity.setY(0); +// newPos.addLocal(velocity); + collideWithWorld(); + + ray.setOrigin(newPos.add(0, footStart, 0)); + ray.setDirection(new Vector3f(0, -1, 0)); +// ray.setLimit(footHeight); + + results.clear(); + scene.collideWith(ray, results); + CollisionResult result = results.getClosestCollision(); + if (result != null){ + newPos.y = result.getContactPoint().getY() + charHeight / 2f; + } + + position.set(newPos); + } + + +} diff --git a/engine/src/test/jme3test/collision/TestMousePick.java b/engine/src/test/jme3test/collision/TestMousePick.java new file mode 100644 index 000000000..1d487f0a4 --- /dev/null +++ b/engine/src/test/jme3test/collision/TestMousePick.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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; +import com.jme3.scene.shape.Sphere; + +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/SolidColor.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/SolidColor.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/SolidColor.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 000000000..961a6d3ca --- /dev/null +++ b/engine/src/test/jme3test/collision/TestRayCasting.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.collision; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.AssetKey; +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.loadAsset(new AssetKey("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 000000000..a3b9b12bc --- /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.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.shape.Box; +import com.jme3.scene.Geometry; +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.scene.Mesh; +import com.jme3.scene.Spatial; + +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/SolidColor.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 000000000..f5b519ea7 --- /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/SimpleTextured.j3md"); + mat1.setTexture("ColorMap", tex); + + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.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 000000000..2191e1166 --- /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 000000000..d30821b00 --- /dev/null +++ b/engine/src/test/jme3test/effect/TestEverything.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.effect; + +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.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.HDRRenderer; +import com.jme3.renderer.Caps; +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.Spatial.CullHint; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.BasicShadowRenderer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; + +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); + 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 000000000..c93c83396 --- /dev/null +++ b/engine/src/test/jme3test/effect/TestExplosionEffect.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.EmitterSphereShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +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(-5f); + flame.setLowLife(.4f); + flame.setHighLife(.5f); + flame.setInitialVelocity(new Vector3f(0, 7, 0)); + flame.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); + 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(-.5f); + 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.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); + spark.setFacingVelocity(true); + spark.setParticlesPerSec(0); + spark.setGravity(5); + spark.setLowLife(1.1f); + spark.setHighLife(1.5f); + spark.setInitialVelocity(new Vector3f(0, 20, 0)); + spark.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(1); + 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(12f); + 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); + 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; +// speed = 0.02f; + 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 000000000..f0cf8b4c6 --- /dev/null +++ b/engine/src/test/jme3test/effect/TestMovingParticle.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.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.EmitterSphereShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +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, 200); + emit.setGravity(0); + emit.setVariation(1); + emit.setLowLife(1); + emit.setHighLife(1); + emit.setStartVel(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); + } + + @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/TestParticleEmitter.java b/engine/src/test/jme3test/effect/TestParticleEmitter.java new file mode 100644 index 000000000..91e4463eb --- /dev/null +++ b/engine/src/test/jme3test/effect/TestParticleEmitter.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.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.EmitterSphereShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; + +public class TestParticleEmitter extends SimpleApplication { + + public static void main(String[] args){ + TestParticleEmitter app = new TestParticleEmitter(); + app.start(); + } + + @Override + public void simpleInitApp() { + ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Triangle, 200); + emit.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + emit.setGravity(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); + + rootNode.attachChild(emit); + +// 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 000000000..d45d70326 --- /dev/null +++ b/engine/src/test/jme3test/effect/TestPointSprite.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 jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.EmitterBoxShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +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() { + 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); + emit.setLowLife(60); + emit.setHighLife(60); + emit.setStartVel(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); + + } + +} diff --git a/engine/src/test/jme3test/export/TestAssetLinkNode.java b/engine/src/test/jme3test/export/TestAssetLinkNode.java new file mode 100644 index 000000000..b15f379bd --- /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 000000000..fb52e100d --- /dev/null +++ b/engine/src/test/jme3test/export/TestOgreConvert.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 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"); +// fis.close(); + + rootNode.attachChild(ogreModelReloaded); + } catch (IOException ex){ + ex.printStackTrace(); + } + } +} diff --git a/engine/src/test/jme3test/gui/TestBitmapFont.java b/engine/src/test/jme3test/gui/TestBitmapFont.java new file mode 100644 index 000000000..f7ca1e788 --- /dev/null +++ b/engine/src/test/jme3test/gui/TestBitmapFont.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 jme3test.gui; + +import net.java.games.input.RawInputEnvironmentPlugin; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +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.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; + +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.setWordWrap(!txt.isWordWrap()); + } + } + }; + + 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() { } + }; + +} diff --git a/engine/src/test/jme3test/gui/TestBitmapText3D.java b/engine/src/test/jme3test/gui/TestBitmapText3D.java new file mode 100644 index 000000000..e8c44c0a7 --- /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 000000000..7e6a8feda --- /dev/null +++ b/engine/src/test/jme3test/gui/TestOrtho.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.gui; + +import com.jme3.app.SimpleApplication; +import com.jme3.math.Vector3f; +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/TestZOrder.java b/engine/src/test/jme3test/gui/TestZOrder.java new file mode 100644 index 000000000..bc4bd8c52 --- /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 000000000..101796bf2 --- /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 000000000..348ee9a88 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloAssets.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 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/SimpleTextured.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 000000000..038783b9c --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloAudio.java @@ -0,0 +1,83 @@ +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/SolidColor.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); + + /* nature sound - keeps playing in a loop. */ + audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", false); + audio_nature.setLooping(true); + audio_nature.setPositional(true); + audio_nature.setLocalTranslation(Vector3f.ZERO.clone()); + audio_nature.setVolume(3); + audio_nature.updateGeometricState(); + audioRenderer.playSource(audio_nature); // play continuously! + } + + /** Declaring the "Shoot" action, and + * 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) { + audioRenderer.playSource(audio_gun); // play 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 000000000..2da6b5e95 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloCollision.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 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 version 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); + + // 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 physics node 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 physics character node. + // The physics character node 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("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("Jumps", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "Lefts"); + inputManager.addListener(this, "Rights"); + inputManager.addListener(this, "Ups"); + inputManager.addListener(this, "Downs"); + inputManager.addListener(this, "Jumps"); + } + + /** 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("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("Jumps")) { + 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 000000000..2539af32c --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloEffects.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.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.setInitialVelocity(new Vector3f(0, 2, 0)); + fire.setStartSize(1.5f); + fire.setEndSize(0.1f); + fire.setGravity(0); + fire.setLowLife(1f); + fire.setHighLife(3f); + fire.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.setRotateSpeed(4); + debris.setSelectRandomImage(true); + debris.setInitialVelocity(new Vector3f(0, 4, 0)); + debris.setStartColor(ColorRGBA.White); + debris.setGravity(6f); + debris.setVelocityVariation(.60f); + rootNode.attachChild(debris); + debris.emitAllParticles(); + +// ParticleEmitter water = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); +// 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(new ColorRGBA(0f, 0f, 1f, 1f)); // blue +// water.setEndColor( new ColorRGBA(0f, 1f, 1f, 1f)); // turquois +// water.setInitialVelocity(new Vector3f(0, -2, 0)); +// water.setStartSize(1f); +// water.setEndSize(1.5f); +// water.setGravity(1); +// water.setLowLife(1f); +// water.setHighLife(3f); +// water.setVelocityVariation(0.3f); +// water.setLocalTranslation(0, 5, 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 000000000..eb6d1e8a5 --- /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.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.math.ColorRGBA; +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; + +/** 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/SolidColor.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*speed, 0); + } + if (name.equals("Right")) { + player.move((new Vector3f(value*speed, 0,0)) ); + } + if (name.equals("Left")) { + player.move(new Vector3f(-value*speed, 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 000000000..6833b1145 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloJME3.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.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.FlyByCamera; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.math.ColorRGBA; + +/** 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 JME3 + } + + @Override + public void simpleInitApp() { + Box b = new Box(Vector3f.ZERO, 1, 1, 1); // create cube shape + Geometry geom = new Geometry("Box", b); // create cube geometry from the shape + Material mat = new Material(assetManager, + "Common/MatDefs/Misc/SolidColor.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 000000000..77d72b4a4 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloLoop.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.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, we make the player character rotate. */ +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/SolidColor.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 000000000..5504598b2 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloMaterial.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.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/SimpleTextured.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/SimpleTextured.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.png")); + mat_lit.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png")); + 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 000000000..d70160b06 --- /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.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Node; + +/** 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/SolidColor.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/SolidColor.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 000000000..6b36ae91b --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloPhysics.java @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.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; + +/** + * 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; + + /** Activate custom rendering of shadows */ + BasicShadowRenderer bsr; + + /** 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); + /** Configure cam to look at scene */ + cam.setLocation(new Vector3f(0, 6f, 6f)); + cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0)); + cam.setFrustumFar(15); + /** Add InputManager action: Left click triggers shooting. */ + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + /** Initialize the scene, materials, and physics space */ + initMaterials(); + initWall(); + initFloor(); + initCrossHairs(); + initShadows(); + } + + /** + * 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/SimpleTextured.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/SimpleTextured.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/SimpleTextured.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.png"); + 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.setShadowMode(ShadowMode.Receive); + 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 < 4; i++) { + Vector3f vt = + new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0); + makeBrick(vt); + } + startpt = -startpt; + height += 2 * brickHeight; + } + } + + /** Activate shadow casting and light direction */ + private void initShadows() { + bsr = new BasicShadowRenderer(assetManager, 256); + bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + viewPort.addProcessor(bsr); + // Default mode is Off -- Every node declares own shadow mode! + rootNode.setShadowMode(ShadowMode.Off); + } + + /** 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 and activate shadows */ + brick_geo.setLocalTranslation(loc); + brick_geo.setShadowMode(ShadowMode.CastAndReceive); + /** 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 and activate shadows */ + ball_geo.setLocalTranslation(cam.getLocation()); + ball_geo.setShadowMode(ShadowMode.CastAndReceive); + /** 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 000000000..6e1dd5a70 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloPicking.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 jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +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/SolidColor.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/SolidColor.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/SolidColor.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 000000000..bffdb58b5 --- /dev/null +++ b/engine/src/test/jme3test/helloworld/HelloTerrain.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 jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.renderer.Camera; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.HillHeightMap; +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; +import jme3tools.converters.ImageToAwt; + +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( + ImageToAwt.convert(heightMapImage.getImage(), false, true, 0)); + heightmap.load(); + + /** 3. We have prepared material and heightmap. Now we create the actual terrain: + * 3.1) We 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) At last, 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 cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + + } +} diff --git a/engine/src/test/jme3test/input/TestChaseCamera.java b/engine/src/test/jme3test/input/TestChaseCamera.java new file mode 100644 index 000000000..230dd9cfa --- /dev/null +++ b/engine/src/test/jme3test/input/TestChaseCamera.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.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.ChaseCamera; +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.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.Quad; +import java.awt.event.KeyEvent; + +public class TestChaseCamera extends SimpleApplication implements AnalogListener, ActionListener { + + private Geometry teaGeom; + private ChaseCamera chaseCam; + private Node pivot; + + 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"); + pivot = new Node("pivot"); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + teaGeom.setMaterial(mat); + pivot.attachChild(teaGeom); + mat = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.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); + pivot.attachChild(ground); + + // Disable the flyby 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 and is the default behavior + //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 + regsiterInput(); + + rootNode.attachChild(pivot); + } + + public void regsiterInput() { + inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP)); + inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN)); + inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT)); + inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT)); + 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 000000000..6967b6bd8 --- /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 000000000..73c8925c9 --- /dev/null +++ b/engine/src/test/jme3test/input/TestJoystick.java @@ -0,0 +1,49 @@ +package jme3test.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.JoyInput; +import com.jme3.input.Joystick; +import com.jme3.input.controls.ActionListener; +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(); + 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 000000000..f0763f770 --- /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 states = new ArrayList(); + 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 000000000..3a623bbe1 --- /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 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 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 000000000..6b43e93e7 --- /dev/null +++ b/engine/src/test/jme3test/input/combomoves/TestComboMoves.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 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 pressedMappings = new HashSet(); + + 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() { + fpsText.setCullHint(CullHint.Always); + statsView.setCullHint(CullHint.Always); + + // 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; + secondCounter = 0; + + // 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 invokedMoves = new ArrayList(); + 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 000000000..d61504305 --- /dev/null +++ b/engine/src/test/jme3test/light/TestEnvironmentMapping.java @@ -0,0 +1,65 @@ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.renderer.RenderManager; +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/TestLightRadius.java b/engine/src/test/jme3test/light/TestLightRadius.java new file mode 100644 index 000000000..f3e22cfcc --- /dev/null +++ b/engine/src/test/jme3test/light/TestLightRadius.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 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 angle; + 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)); + + angle += tpf; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 3f, 2, FastMath.sin(angle) * 3f)); + 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 000000000..edd3af5a2 --- /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 000000000..dc302d52c --- /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/SolidColor.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 000000000..fc287df73 --- /dev/null +++ b/engine/src/test/jme3test/light/TestShadow.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 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/WireColor.j3md")); + 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 000000000..5daf2fa47 --- /dev/null +++ b/engine/src/test/jme3test/light/TestSimpleLighting.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.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.Quaternion; +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/TestTangentGen.java b/engine/src/test/jme3test/light/TestTangentGen.java new file mode 100644 index 000000000..a810d6a42 --- /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/TestTangentGenBadUV.java b/engine/src/test/jme3test/light/TestTangentGenBadUV.java new file mode 100644 index 000000000..7816c71ef --- /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 000000000..123d05f8c --- /dev/null +++ b/engine/src/test/jme3test/light/TestTransparentShadow.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.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +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.Vector2f; +import com.jme3.math.Vector3f; +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/Tree2.mesh.xml"); + 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); + + 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 000000000..1003531fe --- /dev/null +++ b/engine/src/test/jme3test/material/TestBumpModel.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.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.AssetKey; +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", null)); + signpost.setMaterial( (Material) assetManager.loadAsset(new AssetKey("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.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/material/TestColoredTexture.java b/engine/src/test/jme3test/material/TestColoredTexture.java new file mode 100644 index 000000000..8beb771d8 --- /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 000000000..4ad3abe97 --- /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/TestSimpleBumps.java b/engine/src/test/jme3test/material/TestSimpleBumps.java new file mode 100644 index 000000000..df23c9b34 --- /dev/null +++ b/engine/src/test/jme3test/material/TestSimpleBumps.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.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.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 000000000..826ef448d --- /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 000000000..17365d3ae --- /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 000000000..62656c412 --- /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 000000000..7611d1ba0 --- /dev/null +++ b/engine/src/test/jme3test/model/TestMonkeyHead.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.model; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.AssetKey; +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; + +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( (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/model/TestObjLoading.java b/engine/src/test/jme3test/model/TestObjLoading.java new file mode 100644 index 000000000..a94a708f1 --- /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 000000000..52d60da18 --- /dev/null +++ b/engine/src/test/jme3test/model/TestOgreLoading.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; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.scene.Spatial; +import com.jme3.scene.Geometry; +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( (Material) assetManager.loadAsset("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + lightMd2 = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMd2.setMaterial( (Material) assetManager.loadAsset("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 000000000..df487a3f4 --- /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/TestOgreAnim.java b/engine/src/test/jme3test/model/anim/TestOgreAnim.java new file mode 100644 index 000000000..9fb2c6e1a --- /dev/null +++ b/engine/src/test/jme3test/model/anim/TestOgreAnim.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 jme3test.model.anim; + +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.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"); + + 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 = control.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 000000000..6b6a01293 --- /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, new Vector3f(angle, angle, angle)); + +// b2.setUserControl(true); +// b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(angle, angle, angle)); +// + + } + +} 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 000000000..f75d195eb --- /dev/null +++ b/engine/src/test/jme3test/model/shape/TestBillboard.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.model.shape; + +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.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 000000000..cfbeb6785 --- /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/SimpleTextured.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 000000000..a32b234be --- /dev/null +++ b/engine/src/test/jme3test/model/shape/TestCustomMesh.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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; +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 000000000..4765dd7b2 --- /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 000000000..7c34ecf7b --- /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 000000000..7a89c4229 --- /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 = (Material) assetManager.loadAsset("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/TestHostDiscovery.java b/engine/src/test/jme3test/network/TestHostDiscovery.java new file mode 100644 index 000000000..0bd8c4bec --- /dev/null +++ b/engine/src/test/jme3test/network/TestHostDiscovery.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.network; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import java.io.IOException; +import java.net.InetAddress; +import java.util.List; + +public class TestHostDiscovery { + public static void main(String[] args) throws IOException, InterruptedException{ + Server server = new Server(5110, 5110); + server.start(); + + Client client = new Client(); + client.start(); + + List hosts = client.discoverHosts(5110, 5000); + for (InetAddress host : hosts){ + System.out.println("Found host: " + host); + System.out.println("Reachable? " + host.isReachable(5000)); + } + + System.out.println("Connecting to: "+ hosts.get(0)); + client.connect(hosts.get(0).getCanonicalHostName(), 5110, 5110); + } +} diff --git a/engine/src/test/jme3test/network/TestLatency.java b/engine/src/test/jme3test/network/TestLatency.java new file mode 100644 index 000000000..b5657e10b --- /dev/null +++ b/engine/src/test/jme3test/network/TestLatency.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.Message; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.sync.MovingAverage; +import java.io.IOException; + +public class TestLatency extends MessageAdapter { + + 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 Message { + + long timeSent = 0; + long timeReceived = 0; + + public TimestampMessage(){ + setReliable(false); + } + + public TimestampMessage(long timeSent, long timeReceived){ + setReliable(false); + this.timeSent = timeSent; + this.timeReceived = timeReceived; + } + + } + + @Override + public void messageReceived(Message msg){ + TimestampMessage timeMsg = (TimestampMessage) msg; + try { + if (timeMsg.timeReceived == 0){ + TimestampMessage outMsg = new TimestampMessage(timeMsg.timeSent, getTime()); + msg.getClient().send(outMsg); + }else{ + 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)); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Serializer.registerClass(TimestampMessage.class); + + Server server = new Server(5110, 5110); + server.start(); + + client = new Client("localhost", 5110, 5110); + client.start(); + + client.addMessageListener(new TestLatency(), TimestampMessage.class); + server.addMessageListener(new TestLatency(), TimestampMessage.class); + + Thread.sleep(1); + + client.send(new TimestampMessage(getTime(), 0)); + } + +} diff --git a/engine/src/test/jme3test/network/TestMessages.java b/engine/src/test/jme3test/network/TestMessages.java new file mode 100644 index 000000000..98012b76f --- /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.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.Message; +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 Message { + } + + @Serializable + public static class PongMessage extends Message { + } + + private static class PingResponder extends MessageAdapter { + @Override + public void messageReceived(Message message) { + try { + if (message instanceof PingMessage){ + System.out.println("Received ping message!"); + System.out.println("Sending pong message.."); + message.getClient().send(new PongMessage()); + }else if (message instanceof PongMessage){ + System.out.println("Received pong message!"); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Serializer.registerClass(PingMessage.class); + Serializer.registerClass(PongMessage.class); + + Server server = new Server(5110, 5110); + server.start(); + + Client client = new Client("localhost", 5110, 5110); + client.start(); + + server.addMessageListener(new PingResponder(), PingMessage.class); + client.addMessageListener(new PingResponder(), PongMessage.class); + + Thread.sleep(100); + + System.out.println("Sending ping message.."); + client.send(new PingMessage()); + } +} diff --git a/engine/src/test/jme3test/network/TestNetworkStress.java b/engine/src/test/jme3test/network/TestNetworkStress.java new file mode 100644 index 000000000..b5444d04d --- /dev/null +++ b/engine/src/test/jme3test/network/TestNetworkStress.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.network; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.ConnectionAdapter; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestNetworkStress extends ConnectionAdapter { + + @Override + public void clientConnected(Client client) { + System.out.println("CLIENT CONNECTED: "+client.getClientID()); + try { + client.kick("goodbye"); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Logger.getLogger("").getHandlers()[0].setLevel(Level.OFF); + + Server server = new Server(5110, 5110); + server.start(); + server.addConnectionListener(new TestNetworkStress()); + + for (int i = 0; i < 1000; i++){ + Client client = new Client("localhost", 5110, 5110); + client.start(); + + Thread.sleep(10); + } + } +} diff --git a/engine/src/test/jme3test/network/TestRemoteCall.java b/engine/src/test/jme3test/network/TestRemoteCall.java new file mode 100644 index 000000000..3ee28c833 --- /dev/null +++ b/engine/src/test/jme3test/network/TestRemoteCall.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.network; + +import com.jme3.app.SimpleApplication; +import com.jme3.export.Savable; +import com.jme3.network.connection.Client; +import com.jme3.network.connection.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; + + public static interface ServerAccess { + public void attachChild(String model); + } + + public static class ServerAccessImpl implements ServerAccess { + public void attachChild(String model) { + final String finalModel = model; + serverApp.enqueue(new Callable() { + public Void call() throws Exception { + Spatial spatial = serverApp.getAssetManager().loadModel(finalModel); + serverApp.getRootNode().attachChild(spatial); + return null; + } + }); + } + } + + public static void createServer(){ + serverApp = new SimpleApplication() { + @Override + public void simpleInitApp() { + } + }; + serverApp.start(); + + try { + Server server = new Server(5110, 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 = new Client("localhost", 5110, 5110); + client.start(); + + ObjectStore store = new ObjectStore(client); + ServerAccess access = store.getExposedObject("access", ServerAccess.class, true); + access.attachChild("Models/Ferrari/WheelBackLeft.mesh.xml"); + } +} diff --git a/engine/src/test/jme3test/network/TestSerialization.java b/engine/src/test/jme3test/network/TestSerialization.java new file mode 100644 index 000000000..36e0076f4 --- /dev/null +++ b/engine/src/test/jme3test/network/TestSerialization.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.network; + +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.events.MessageAdapter; +import com.jme3.network.message.Message; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestSerialization extends MessageAdapter { + + @Serializable + public static class SomeObject { + + private int val; + + public SomeObject(){ + } + + public SomeObject(int val){ + this.val = val; + } + + public int getVal(){ + return val; + } + + public String toString(){ + return "SomeObject[val="+val+"]"; + } + } + + public enum Status { + High, + Middle, + Low; + } + + @Serializable + public static class TestSerializationMessage extends Message { + + boolean z; + byte b; + char c; + short s; + int i; + float f; + long l; + double d; + + int[] ia; + List ls; + Map 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(); + ls.add("hello"); + ls.add(new SomeObject(-22)); + + mp = new HashMap(); + mp.put("abc", new SomeObject(555)); + + status1 = Status.High; + status2 = Status.Middle; + + date = new Date(System.currentTimeMillis()); + } + } + } + + @Override + public void messageReceived(Message msg){ + TestSerializationMessage cm = (TestSerializationMessage) msg; + 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 = new Server(5110, 5110); + server.start(); + + Client client = new Client("localhost", 5110, 5110); + client.start(); + + Thread.sleep(100); + + server.addMessageListener(new TestSerialization(), TestSerializationMessage.class); + client.send(new TestSerializationMessage(true)); + } + +} diff --git a/engine/src/test/jme3test/network/sync/BoxEntity.java b/engine/src/test/jme3test/network/sync/BoxEntity.java new file mode 100644 index 000000000..148ec00f6 --- /dev/null +++ b/engine/src/test/jme3test/network/sync/BoxEntity.java @@ -0,0 +1,47 @@ +package jme3test.network.sync; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.network.sync.Sync; +import com.jme3.network.sync.SyncEntity; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +public class BoxEntity extends Geometry implements SyncEntity { + + protected @Sync Vector3f pos; + protected @Sync Vector3f vel; + + public BoxEntity(AssetManager assetManager, ColorRGBA color){ + super("Box", new Box(1,1,1)); + setMaterial(new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md")); + getMaterial().setColor("Color", color); + } + + public void setPosVel(Vector3f pos, Vector3f vel){ + setLocalTranslation(pos); + this.pos = pos; + this.vel = vel; + } + + public void onRemoteCreate() { + } + + public void onRemoteUpdate(float latencyDelta) { + } + + public void onRemoteDelete() { + removeFromParent(); + } + + public void onLocalUpdate() { + } + + public void interpolate(float blendAmount) { + } + + public void extrapolate(float tpf) { + } +} diff --git a/engine/src/test/jme3test/network/sync/ClientBoxEntity.java b/engine/src/test/jme3test/network/sync/ClientBoxEntity.java new file mode 100644 index 000000000..fb02b4f83 --- /dev/null +++ b/engine/src/test/jme3test/network/sync/ClientBoxEntity.java @@ -0,0 +1,68 @@ +package jme3test.network.sync; + +import com.jme3.asset.AssetManager; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +public class ClientBoxEntity extends BoxEntity { + + private Vector3f prevPos = new Vector3f(); + + public ClientBoxEntity(AssetManager assetManager, ColorRGBA color){ + super(assetManager, color); + } + + @Override + public void onRemoteCreate() { + System.out.println("ClientBoxEntity created"); + } + + @Override + public void onRemoteDelete() { + System.out.println("ClientBoxEntity deleted"); + } + + @Override + public void onRemoteUpdate(float latencyDelta) { + prevPos.set(getLocalTranslation()); + pos.addLocal(vel.mult(latencyDelta)); + } + + private static float interpolateCubic(float v0, float v1, float v2, float v3, float x){ + float p = (v3 - v2) - (v0 - v1); + float q = (v0 - v1) - p; + float r = v2 - v0; + float s = v1; + + return p * x * x * x + + q * x * x + + r * x + + s; + } + + private static Vector3f interpolateCubic(Vector3f v0, Vector3f v1, + Vector3f v2, Vector3f v3, + float x){ + Vector3f vec = new Vector3f(); + vec.x = interpolateCubic(v0.x, v1.x, v2.x, v3.x, x); + vec.y = interpolateCubic(v0.y, v1.y, v2.y, v3.y, x); + vec.z = interpolateCubic(v0.z, v1.z, v2.z, v3.z, x); + return vec; + } + + @Override + public void interpolate(float blendAmount) { + if (pos != null){ + getLocalTranslation().interpolate(prevPos, pos, blendAmount); + setLocalTranslation(getLocalTranslation()); + } + } + + @Override + public void extrapolate(float tpf) { + if (pos != null){ + pos.addLocal(vel.mult(tpf)); + setLocalTranslation(pos); + } + } + } \ No newline at end of file diff --git a/engine/src/test/jme3test/network/sync/TestSync.java b/engine/src/test/jme3test/network/sync/TestSync.java new file mode 100644 index 000000000..e1f9396df --- /dev/null +++ b/engine/src/test/jme3test/network/sync/TestSync.java @@ -0,0 +1,113 @@ +package jme3test.network.sync; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.EmitterSphereShape; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.network.connection.Client; +import com.jme3.network.connection.Server; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.sync.ClientSyncService; +import com.jme3.network.sync.EntityFactory; +import com.jme3.network.sync.ServerSyncService; +import com.jme3.network.sync.SyncEntity; +import com.jme3.network.sync.SyncMessage; +import java.io.IOException; + +public class TestSync extends SimpleApplication implements EntityFactory { + + // Client Variables + private Client client; + private ClientSyncService clientSyncServ; + + // Server Variables + private Server server; + private ServerSyncService serverSyncServ; + private BoxEntity serverBox; + + private Vector3f targetPos = new Vector3f(); + private float boxSpeed = 3f; + private EmitterSphereShape randomPosSphere = new EmitterSphereShape(Vector3f.ZERO, 5); + + public static void main(String[] args){ + TestSync app = new TestSync(); + app.start(); + } + + public void simpleInitApp(){ + Serializer.registerClass(SyncMessage.class); + + // ----- Start Server ------- + try { + server = new Server(5110, 5110); + server.start(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + // Create SyncService for server + serverSyncServ = server.getService(ServerSyncService.class); + // Create server box entity (red) + serverBox = new BoxEntity(assetManager, ColorRGBA.Red); + serverSyncServ.addNpc(serverBox); + rootNode.attachChild(serverBox); + + // Enable 10% packet drop rate and 200 ms latency + serverSyncServ.setNetworkSimulationParams(0.1f, 200); + + + // ------ Start Client ------- + try { + client = new Client("localhost", 5110, 5110); + client.start(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + clientSyncServ = client.getService(ClientSyncService.class); + clientSyncServ.setEntityFactory(this); + } + + /** + * Create a new entity (for client) + * @param entityType + * @return + */ + public SyncEntity createEntity(Class entityType) { + BoxEntity clientBox = new ClientBoxEntity(assetManager, ColorRGBA.Green); + rootNode.attachChild(clientBox); + return clientBox; + } + + @Override + public void simpleUpdate(float tpf){ + // --------- Update Client Sync --------- + clientSyncServ.update(tpf); + + // --------- Update Server Sync --------- + // if needed determine next box position + if (serverBox.getLocalTranslation().distance(targetPos) < 0.1f){ + randomPosSphere.getRandomPoint(targetPos); + }else{ + Vector3f velocity = new Vector3f(targetPos); + velocity.subtractLocal(serverBox.getLocalTranslation()); + velocity.normalizeLocal().multLocal(boxSpeed); + + Vector3f newPos = serverBox.getLocalTranslation().clone(); + newPos.addLocal(velocity.mult(tpf)); + serverBox.setPosVel(newPos, velocity); + } + + serverSyncServ.update(tpf); + } + + @Override + public void destroy(){ + super.destroy(); + try { + client.disconnect(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } +} diff --git a/engine/src/test/jme3test/niftygui/TestNiftyGui.java b/engine/src/test/jme3test/niftygui/TestNiftyGui.java new file mode 100644 index 000000000..5c377d186 --- /dev/null +++ b/engine/src/test/jme3test/niftygui/TestNiftyGui.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 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; + +public class TestNiftyGui extends SimpleApplication { + + 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/SimpleTextured.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("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); +// flyCam.setDragToRotate(true); + } + +} diff --git a/engine/src/test/jme3test/niftygui/TestNiftyToMesh.java b/engine/src/test/jme3test/niftygui/TestNiftyToMesh.java new file mode 100644 index 000000000..33e5c714f --- /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.setClearEnabled(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.setClearEnabled(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/niftygui/hellojme.xml b/engine/src/test/jme3test/niftygui/hellojme.xml new file mode 100644 index 000000000..7a8605849 --- /dev/null +++ b/engine/src/test/jme3test/niftygui/hellojme.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/engine/src/test/jme3test/post/BloomUI.java b/engine/src/test/jme3test/post/BloomUI.java new file mode 100644 index 000000000..5029732ad --- /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 000000000..22cc8c7fc --- /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 000000000..21509f587 --- /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 000000000..1c371b491 --- /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 000000000..a57656de5 --- /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/TestDepthOfField.java b/engine/src/test/jme3test/post/TestDepthOfField.java new file mode 100644 index 000000000..580fe5acd --- /dev/null +++ b/engine/src/test/jme3test/post/TestDepthOfField.java @@ -0,0 +1,213 @@ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +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.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; + +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; +import jme3tools.converters.ImageToAwt; + +/** + * 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; + Material matWire; + 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.png"); + 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); + + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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 000000000..fee11feb5 --- /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 000000000..220a07004 --- /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 000000000..df1714cf7 --- /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.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.post.HDRRenderer; +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/SimpleTextured.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 000000000..cc5bfbd1e --- /dev/null +++ b/engine/src/test/jme3test/post/TestLightScattering.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.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(); + } + + public void loadFPSText(){ + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + fpsText = new BitmapText(guiFont, false); + fpsText.setSize(guiFont.getCharSet().getRenderedSize()); + fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); + fpsText.setText("Frames per second"); + guiNode.attachChild(fpsText); + } + + public void loadStatsView(){ + statsView = new StatsView("Statistics View", assetManager, renderer.getStatistics()); +// move it up so it appears above fps text + statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0); + guiNode.attachChild(statsView); + } + + @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/SimpleTextured.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 000000000..8e200e635 --- /dev/null +++ b/engine/src/test/jme3test/post/TestMultiRenderTarget.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.post; + +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.Matrix4f; +import com.jme3.math.Quaternion; +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.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.setClearEnabled(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/TestMultiplesFilters.java b/engine/src/test/jme3test/post/TestMultiplesFilters.java new file mode 100644 index 000000000..39cf43f90 --- /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.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.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 com.jme3.input.controls.ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if ("toggleSSAO".equals(name) && isPressed) { + if (fpp.isFilterEnabled(ssaoFilter)) { + fpp.setFilterEnabled(ssaoFilter, false); + } else { + fpp.setFilterEnabled(ssaoFilter, true); + } + } + if ("toggleWater".equals(name) && isPressed) { + if (fpp.isFilterEnabled(water)) { + fpp.setFilterEnabled(water, false); + } else { + fpp.setFilterEnabled(water, true); + } + } + if ("toggleBloom".equals(name) && isPressed) { + if (fpp.isFilterEnabled(bloom)) { + fpp.setFilterEnabled(bloom, false); + } else { + fpp.setFilterEnabled(bloom, true); + } + } + if ("toggleOverlay".equals(name) && isPressed) { + if (fpp.isFilterEnabled(overlay)) { + fpp.setFilterEnabled(overlay, false); + } else { + fpp.setFilterEnabled(overlay, 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 000000000..917180db6 --- /dev/null +++ b/engine/src/test/jme3test/post/TestPostFilters.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 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.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.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/TestRenderToMemory.java b/engine/src/test/jme3test/post/TestRenderToMemory.java new file mode 100644 index 000000000..12892cbf5 --- /dev/null +++ b/engine/src/test/jme3test/post/TestRenderToMemory.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 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.awt.image.DataBufferByte; +import java.awt.image.WritableRaster; +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.setClearEnabled(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 000000000..f61af99da --- /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.setClearEnabled(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/SimpleTextured.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 000000000..e9cef98f2 --- /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 000000000..300c937fa --- /dev/null +++ b/engine/src/test/jme3test/post/TestTransparentCartoonEdge.java @@ -0,0 +1,101 @@ +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.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.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/Tree2.mesh.xml"); + 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 000000000..828067a8c --- /dev/null +++ b/engine/src/test/jme3test/post/TestTransparentSSAO.java @@ -0,0 +1,78 @@ +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.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.ssao.SSAOFilter; +import com.jme3.renderer.queue.RenderQueue.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/Tree2.mesh.xml"); + 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 000000000..64be9a8c9 --- /dev/null +++ b/engine/src/test/jme3test/renderer/TestMultiViews.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.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.setClearEnabled(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.setClearEnabled(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.setClearEnabled(true); + view4.attachScene(rootNode); + } +} diff --git a/engine/src/test/jme3test/renderer/TestParallelProjection.java b/engine/src/test/jme3test/renderer/TestParallelProjection.java new file mode 100644 index 000000000..a390a3f74 --- /dev/null +++ b/engine/src/test/jme3test/renderer/TestParallelProjection.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 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.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial.CullHint; + +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 000000000..66a16645d --- /dev/null +++ b/engine/src/test/jme3test/scene/TestSceneLoading.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.scene; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.material.Material; +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.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Texture; +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) { + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + 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)); + + // 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 000000000..10bafd6b6 --- /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/TestLeakingGL.java b/engine/src/test/jme3test/stress/TestLeakingGL.java new file mode 100644 index 000000000..343b6e045 --- /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.renderer.GLObjectManager; +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 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 = (Material) assetManager.loadAsset("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(GLObjectManager.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 000000000..abe78e077 --- /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.input.KeyInput; +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 { + + private boolean lod = false; + + public static void main(String[] args){ + TestLodStress app = new TestLodStress(); + 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"); + + 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/TerrainTest.java b/engine/src/test/jme3test/terrain/TerrainTest.java new file mode 100644 index 000000000..1f5149044 --- /dev/null +++ b/engine/src/test/jme3test/terrain/TerrainTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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 jme3tools.converters.ImageToAwt; +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.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.LodPerspectiveCalculatorFactory; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.util.ArrayList; +import java.util.List; + +/** + * 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/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3); + + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 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());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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); + + getCamera().getLocation().y = 10; + getCamera().setDirection(new Vector3f(0, -1.5f, -1)); + } + + 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 000000000..90ecdb6c5 --- /dev/null +++ b/engine/src/test/jme3test/terrain/TerrainTestAdvanced.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 jme3test.terrain; + +import jme3tools.converters.ImageToAwt; +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.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.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.LodPerspectiveCalculatorFactory; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @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.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.png"); + 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); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + + createSky(); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3); + + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 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());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + 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); + + 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); + + getCamera().getLocation().y = 10; + getCamera().setDirection(new Vector3f(0, -1.5f, -1)); + } + + 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)); + } else { + matTerrain.setBoolean("useTriPlanarMapping", false); + matTerrain.setFloat("DiffuseMap_0_scale", grassScale); + matTerrain.setFloat("DiffuseMap_1_scale", dirtScale); + matTerrain.setFloat("DiffuseMap_2_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); + } +} diff --git a/engine/src/test/jme3test/terrain/TerrainTestCollision.java b/engine/src/test/jme3test/terrain/TerrainTestCollision.java new file mode 100644 index 000000000..f9e910a4e --- /dev/null +++ b/engine/src/test/jme3test/terrain/TerrainTestCollision.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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 jme3tools.converters.ImageToAwt; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.BulletAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingSphere; +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.renderer.Camera; +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.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.util.ArrayList; +import java.util.List; + +/** + * 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/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f); + heightmap.load(); + + } catch (Exception e) { } + + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(2, 2, 2)); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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); + + getCamera().getLocation().y = 25; + getCamera().setDirection(new Vector3f(0, -1, 0)); + } + + 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/SolidColor.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().setDirection(new Vector3f(0,-1,0)); + } + 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 000000000..a613a35b0 --- /dev/null +++ b/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.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 jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +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.AmbientLight; +import com.jme3.light.DirectionalLight; +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.renderer.Camera; +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; +import jme3tools.converters.ImageToAwt; + +/** + * + * @author Brent Owens + */ +public class TerrainTestModifyHeight extends SimpleApplication { + + private TerrainQuad terrain; + Material matTerrain; + Material matWire; + boolean wireframe = false; + boolean triPlanar = false; + boolean wardiso = false; + boolean minnaert = false; + protected BitmapText hintText; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + + public static void main(String[] args) { + TerrainTestModifyHeight app = new TerrainTestModifyHeight(); + app.start(); + } + + + @Override + public void initialize() { + super.initialize(); + + loadHintText(); + initCrossHairs(); + } + + @Override + public void update() { + super.update(); + + updateHintText(); + } + + @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.setBoolean("WardIso", true); + + // 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); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + + // CREATE THE TERRAIN + terrain = new TerrainQuad("terrain", 65, 513, null); + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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); + + AmbientLight ambLight = new AmbientLight(); + ambLight.setColor(new ColorRGBA(1f, 1f, 0.8f, 0.2f)); + rootNode.addLight(ambLight); + + getCamera().getLocation().y = 10; + getCamera().setDirection(new Vector3f(0, -1.5f, -1)); + } + + public void loadHintText() { + hintText = new BitmapText(guiFont, false); + hintText.setSize(guiFont.getCharSet().getRenderedSize()); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Hit 1 to raise terrain, hit 2 to lower terrain"); + guiNode.attachChild(hintText); + } + + public void updateHintText() { + int x = (int) getCamera().getLocation().x; + int y = (int) getCamera().getLocation().y; + int z = (int) getCamera().getLocation().z; + hintText.setText("Hit 1 to raise terrain, hit 2 to lower terrain. "+x+","+y+","+z); + } + + 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(100); + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(actionListener, "wireframe"); + inputManager.addMapping("Raise", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addListener(actionListener, "Raise"); + inputManager.addMapping("Lower", new KeyTrigger(KeyInput.KEY_2)); + 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")) { + if (pressed) { + Vector3f intersection = getWorldIntersection(); + if (intersection != null) { + adjustHeight(intersection, 16, 1); + } + } + } else if (name.equals("Lower")) { + if (pressed) { + Vector3f intersection = getWorldIntersection(); + if (intersection != null) { + adjustHeight(intersection, 16, -1); + } + } + } + + } + }; + + 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(); + for (int z=-radiusStepsZ; z 0) { + CollisionResult hit = results.getClosestCollision(); + return hit.getContactPoint(); + } + return null; + } +} diff --git a/engine/src/test/jme3test/terrain/TerrainTestReadWrite.java b/engine/src/test/jme3test/terrain/TerrainTestReadWrite.java new file mode 100644 index 000000000..8703fcc7f --- /dev/null +++ b/engine/src/test/jme3test/terrain/TerrainTestReadWrite.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. + */ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +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.renderer.Camera; +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.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3tools.converters.ImageToAwt; + +/** + * Saves and loads terrain. + * + * @author Brent Owens + */ +public class TerrainTestReadWrite extends SimpleApplication { + + private TerrainQuad 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.png"); + 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/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3); + + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 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 + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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); + } + + /** + * 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(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) { + terrain.removeFromParent(); + terrain.removeControl(TerrainLodControl.class); + terrain.detachAllChildren(); + terrain = null; + } + + // import the saved terrain, and attach it back to the root node + fis = new FileInputStream(new File("terrainsave.jme")); + BinaryImporter imp = BinaryImporter.getInstance(); + imp.setAssetManager(assetManager); + terrain = (TerrainQuad) imp.load(new BufferedInputStream(fis)); + rootNode.attachChild(terrain); + + float duration = (System.currentTimeMillis() - start) / 1000.0f; + System.out.println("Load took " + duration + " seconds"); + + // now we have to add back the cameras to the LOD control, since we didn't want to duplicate them on save + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl lodControl = terrain.getControl(TerrainLodControl.class); + if (lodControl != null) { + lodControl.setCameras(cameras); + } + + } 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) { + + TerrainQuad clone = terrain.clone(); + terrain.removeFromParent(); + terrain = clone; + getRootNode().attachChild(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/TestDdsLoading.java b/engine/src/test/jme3test/texture/TestDdsLoading.java new file mode 100644 index 000000000..87755c255 --- /dev/null +++ b/engine/src/test/jme3test/texture/TestDdsLoading.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.texture; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; + +public class TestDdsLoading extends SimpleApplication { + + public static void main(String[] args){ + TestDdsLoading app = new TestDdsLoading(); + app.start(); + } + + @Override + public void simpleInitApp() { + // create a simple plane/quad + Quad quadMesh = new Quad(1, 1); + + Geometry quad = new Geometry("Textured Quad", quadMesh); + Texture tex = assetManager.loadTexture("Textures/Sky/Night/Night_dxt1.dds"); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + mat.setTexture("ColorMap", tex); + quad.setMaterial(mat); + + float aspect = tex.getImage().getWidth() / (float) tex.getImage().getHeight(); + quad.setLocalScale(new Vector3f(aspect * 5, 5, 1)); + quad.center(); + + rootNode.attachChild(quad); + } + +} diff --git a/engine/src/test/jme3test/texture/TestNormalLatc.java b/engine/src/test/jme3test/texture/TestNormalLatc.java new file mode 100644 index 000000000..24a97f6c4 --- /dev/null +++ b/engine/src/test/jme3test/texture/TestNormalLatc.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 jme3test.texture; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +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; + +public class TestNormalLatc extends SimpleApplication { + + private Quad quadMesh; + + public static void main(String[] args){ + TestNormalLatc app = new TestNormalLatc(); + app.start(); + } + + public Geometry createQuad(float side, String texName, boolean latc){ + Geometry quad = new Geometry("Textured Quad", quadMesh); + + TextureKey key = new TextureKey(texName, true); + key.setGenerateMips(false); + Texture tex = assetManager.loadTexture(key); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + mat.setTexture("ColorMap", tex); +// mat.setBoolean("Normalize", true); + if (latc) + mat.setBoolean("LATC", true); + + quad.setMaterial(mat); + + float aspect = tex.getImage().getWidth() / (float) tex.getImage().getHeight(); + quad.setLocalScale(new Vector3f(aspect * 5, 5, 1)); + quad.center(); + quad.setLocalTranslation(quad.getLocalTranslation().x + quad.getLocalScale().x * side, 0, 0); + + return quad; + } + + public void simpleInitApp() { + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText txt = new BitmapText(font, false); + txt.setText("Left: LATC, Middle: JPG, Right: DXT1nm"); + txt.setLocalTranslation(0, txt.getLineHeight() * 2, 0); + guiNode.attachChild(txt); + + // create a simple plane/quad + quadMesh = new Quad(1, 1); + quadMesh.updateGeometry(1, 1, false); + + rootNode.attachChild(createQuad(-1f, "Textures/BumpMapTest/Dot3_latc.dds", true)); + rootNode.attachChild(createQuad(0f, "Textures/BumpMapTest/Dot3.jpg", false)); + rootNode.attachChild(createQuad(1f, "Textures/BumpMapTest/Dot3_dxt1.dds", false)); + } + +} diff --git a/engine/src/test/jme3test/texture/TestSkyLoading.java b/engine/src/test/jme3test/texture/TestSkyLoading.java new file mode 100644 index 000000000..7101f1be9 --- /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/TestYCoCgDds.java b/engine/src/test/jme3test/texture/TestYCoCgDds.java new file mode 100644 index 000000000..c830437f6 --- /dev/null +++ b/engine/src/test/jme3test/texture/TestYCoCgDds.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 jme3test.texture; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +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; + +/** + * Compares RGB8, DXT5-YCoCg and DXT1 with a skybox texture + * @author Kirill + */ +public class TestYCoCgDds extends SimpleApplication { + + private Quad quadMesh; + + public static void main(String[] args){ + TestYCoCgDds app = new TestYCoCgDds(); + app.start(); + } + + public Geometry createQuad(float side, String texName, boolean ycocg){ + Geometry quad = new Geometry("Textured Quad", quadMesh); + + Texture tex = assetManager.loadTexture(texName); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + mat.setTexture("ColorMap", tex); + if (ycocg) + mat.setBoolean("YCoCg", true); + + quad.setMaterial(mat); + + float aspect = tex.getImage().getWidth() / (float) tex.getImage().getHeight(); + quad.setLocalScale(new Vector3f(aspect * 5, 5, 1)); + quad.center(); + quad.setLocalTranslation(quad.getLocalTranslation().x + quad.getLocalScale().x * side, 0, 0); + + return quad; + } + + public void simpleInitApp() { + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText txt = new BitmapText(font, false); + txt.setText("Left: Original, Middle: DXT5-YCoCg, Right: DXT1"); + txt.setLocalTranslation(0, txt.getLineHeight() * 2, 0); + guiNode.attachChild(txt); + + // create a simple plane/quad + quadMesh = new Quad(1, 1); + quadMesh.updateGeometry(1, 1, false); + + rootNode.attachChild(createQuad(-1f, "Textures/Sky/Night/Night.png", false)); + rootNode.attachChild(createQuad(0, "Textures/Sky/Night/Night_ycc.dds", true)); + rootNode.attachChild(createQuad(1f, "Textures/Sky/Night/Night_dxt1.dds", false)); + } + +} diff --git a/engine/src/test/jme3test/water/TestPostWater.java b/engine/src/test/jme3test/water/TestPostWater.java new file mode 100644 index 000000000..32a9d165f --- /dev/null +++ b/engine/src/test/jme3test/water/TestPostWater.java @@ -0,0 +1,187 @@ +package jme3test.water; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.bounding.BoundingBox; +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.post.FilterPostProcessor; +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.texture.Texture2D; +import com.jme3.util.SkyFactory; +import com.jme3.water.WaterFilter; +import java.util.ArrayList; +import java.util.List; +import jme3tools.converters.ImageToAwt; + +/** + * test + * @author normenhansen + */ +public class TestPostWater extends SimpleApplication { + + private FilterPostProcessor fpp; + private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + private WaterFilter water; + TerrainQuad terrain; + Material matRock; + Material matWire; + + public static void main(String[] args) { + TestPostWater app = new TestPostWater(); + 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.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); + cam.setFrustumFar(4000); + //cam.setFrustumNear(100); + AudioNode waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", false); + waves.setLooping(true); + audioRenderer.playSource(waves); + + + fpp = new FilterPostProcessor(assetManager); + // fpp.setNumSamples(4); + + water = new WaterFilter(rootNode, lightDir); + 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); + fpp.addFilter(water); + 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)); + } + + 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.png"); + 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); + + matWire = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md"); + matWire.setColor("Color", ColorRGBA.Green); + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5,5,5)); + terrain.setLocalTranslation(new Vector3f(0,-30,0)); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + 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; + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + time += tpf; + waterHeight = (float) Math.cos(((time * 0.6f) % FastMath.TWO_PI)) * 1.5f; + water.setWaterHeight(initialWaterHeight + waterHeight); + } +} diff --git a/engine/src/test/jme3test/water/TestPostWaterLake.java b/engine/src/test/jme3test/water/TestPostWaterLake.java new file mode 100644 index 000000000..96cf1987c --- /dev/null +++ b/engine/src/test/jme3test/water/TestPostWaterLake.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.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.Quaternion; +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) { + File file = new File("wildhouse.zip"); + if (file.exists()) { + useHttp = false; + } + 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)); + + // 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 000000000..5837a4b71 --- /dev/null +++ b/engine/src/test/jme3test/water/TestSceneWater.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.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.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +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.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) { + File file = new File("wildhouse.zip"); + if (file.exists()) { + useHttp = false; + } + 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)); + + // 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/SimpleTextured.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 000000000..43f146436 --- /dev/null +++ b/engine/src/test/jme3test/water/TestSimpleWater.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 jme3test.water; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.queue.RenderQueue.Bucket; +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.texture.Texture; +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/SimpleTextured.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 000000000..855b53f1d --- /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 000000000..8e8df5682 --- /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 { + public T convert(T input, Map params); +} diff --git a/engine/src/tools/jme3tools/converters/FolderConverter.java b/engine/src/tools/jme3tools/converters/FolderConverter.java new file mode 100644 index 000000000..8dc9922db --- /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 "); + 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/ImageToAwt.java b/engine/src/tools/jme3tools/converters/ImageToAwt.java new file mode 100644 index 000000000..2af614052 --- /dev/null +++ b/engine/src/tools/jme3tools/converters/ImageToAwt.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; + +public class ImageToAwt { + + private static final HashMap params = new HashMap(); + + 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(); + + 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)); + } + + 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); + + 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/tools/jme3tools/converters/MipMapGenerator.java b/engine/src/tools/jme3tools/converters/MipMapGenerator.java new file mode 100644 index 000000000..7b656a9ee --- /dev/null +++ b/engine/src/tools/jme3tools/converters/MipMapGenerator.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; + +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_BICUBIC); + g.drawImage(sourceImage, 0, 0, targetWidth, targetHeight, 0, 0, sourceWidth, sourceHeight, null); + g.dispose(); + + return targetImage; + } + + 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 output = new ArrayList(); + 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/tools/jme3tools/converters/RGB565.java b/engine/src/tools/jme3tools/converters/RGB565.java new file mode 100644 index 000000000..c0e43f560 --- /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 000000000..b2d3a4e65 --- /dev/null +++ b/engine/src/tools/jme3tools/converters/model/FloatToFixed.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 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.IndexBuffer; +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.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +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 000000000..5e94bdd97 --- /dev/null +++ b/engine/src/tools/jme3tools/converters/model/ModelConverter.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 jme3tools.converters.model; + +import com.jme3.scene.Geometry; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import jme3tools.converters.model.strip.PrimitiveGroup; +import jme3tools.converters.model.strip.TriStrip; +import java.nio.Buffer; +import java.util.Arrays; +import java.util.Comparator; + +public class ModelConverter { + + private static final class PrimComparator + implements Comparator { + + 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 bufs = mesh.getBuffers(); + for (Entry 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 000000000..02b735099 --- /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 000000000..f289d78b4 --- /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 { + + 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 000000000..e917ec708 --- /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 000000000..8319280c3 --- /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 { + + + 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 000000000..7e7eecbc2 --- /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 000000000..51697ee41 --- /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 000000000..a9b1098a5 --- /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 000000000..58165f99c --- /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 { + + + 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 000000000..69f515785 --- /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 000000000..c8630fe04 --- /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 resetPoints = new HashSet(); /* 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 000000000..aa84d5cb1 --- /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: + *
    + *
  • generates strips from arbitrary geometry. + *
  • flexibly optimizes for post TnL vertex caches (16 on GeForce1/2, 24 on GeForce3). + *
  • can stitch together strips using degenerate triangles, or not. + *
  • can output lists instead of strips. + *
  • can optionally throw excessively small strips into a list instead. + *
  • can remap indices to improve spatial locality in your vertex buffers. + *
+ * 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 + */ +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 000000000..b60e9e54c --- /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/navigation/Coordinate.java b/engine/src/tools/jme3tools/navigation/Coordinate.java new file mode 100644 index 000000000..69feb7c72 --- /dev/null +++ b/engine/src/tools/jme3tools/navigation/Coordinate.java @@ -0,0 +1,249 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3tools.navigation; + +import jme3tools.navigation.InvalidPositionException; +import java.text.DecimalFormat; +import jme3tools.navigation.StringUtil; +import jme3tools.navigation.NumUtil; + +/** + * 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; + } + 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/tools/jme3tools/navigation/GCSailing.java b/engine/src/tools/jme3tools/navigation/GCSailing.java new file mode 100644 index 000000000..f5699b591 --- /dev/null +++ b/engine/src/tools/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/tools/jme3tools/navigation/InvalidPositionException.java b/engine/src/tools/jme3tools/navigation/InvalidPositionException.java new file mode 100644 index 000000000..32b3c9bd5 --- /dev/null +++ b/engine/src/tools/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/tools/jme3tools/navigation/MapModel2D.java b/engine/src/tools/jme3tools/navigation/MapModel2D.java new file mode 100644 index 000000000..1e6ec362b --- /dev/null +++ b/engine/src/tools/jme3tools/navigation/MapModel2D.java @@ -0,0 +1,371 @@ +package jme3tools.navigation; +import jme3tools.navigation.InvalidPositionException; +import java.awt.Point; +import java.text.DecimalFormat; +import jme3tools.navigation.Position; +import jme3tools.navigation.NavCalculator; + +/* + * 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 {@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 Point 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 {@code 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/tools/jme3tools/navigation/MapModel3D.java b/engine/src/tools/jme3tools/navigation/MapModel3D.java new file mode 100644 index 000000000..de20cb6b9 --- /dev/null +++ b/engine/src/tools/jme3tools/navigation/MapModel3D.java @@ -0,0 +1,393 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3tools.navigation; + +import com.jme3.math.Vector3f; +import jme3tools.navigation.Position; +import jme3tools.navigation.InvalidPositionException; +import java.awt.Point; +import java.text.DecimalFormat; +import jme3tools.navigation.NavCalculator; + + +/** + * 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 viewportWidth 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 Position 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 Position to convert. + * @return The Point 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 p Vector containing the world unit + * coordinates that are to be converted into + * longitude / latitude coordinates. + * @return The resulting Position 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 p Vector3f 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 Vector3f object marking the map's (x,y) centre. + * @since 1.0 + */ + public Vector3f getCentreWu() { + return new Vector3f(xCentre, 0, zCentre); + } + + /** + * Returns the Position centre of the map. + * + * @return 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 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/tools/jme3tools/navigation/NavCalculator.java b/engine/src/tools/jme3tools/navigation/NavCalculator.java new file mode 100644 index 000000000..1a641f086 --- /dev/null +++ b/engine/src/tools/jme3tools/navigation/NavCalculator.java @@ -0,0 +1,592 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3tools.navigation; + +import jme3tools.navigation.InvalidPositionException; +import jme3tools.navigation.Position; + + + +/** + * 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); + case GC: + greatCircleSailing(P1, P2); + } + } + + /** + * 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 dLong + * @param dLat + * @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/tools/jme3tools/navigation/NumUtil.java b/engine/src/tools/jme3tools/navigation/NumUtil.java new file mode 100644 index 000000000..086fff249 --- /dev/null +++ b/engine/src/tools/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/tools/jme3tools/navigation/Position.java b/engine/src/tools/jme3tools/navigation/Position.java new file mode 100644 index 000000000..b26e4bca1 --- /dev/null +++ b/engine/src/tools/jme3tools/navigation/Position.java @@ -0,0 +1,230 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3tools.navigation; + +import jme3tools.navigation.InvalidPositionException; + +/** + * 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/tools/jme3tools/navigation/RLSailing.java b/engine/src/tools/jme3tools/navigation/RLSailing.java new file mode 100644 index 000000000..1a9a91ccb --- /dev/null +++ b/engine/src/tools/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/tools/jme3tools/navigation/StringUtil.java b/engine/src/tools/jme3tools/navigation/StringUtil.java new file mode 100644 index 000000000..35765eac7 --- /dev/null +++ b/engine/src/tools/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.*; + +/** + * 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/tools/jme3tools/nvtex/NVCompress.form b/engine/src/tools/jme3tools/nvtex/NVCompress.form new file mode 100644 index 000000000..2d5754f00 --- /dev/null +++ b/engine/src/tools/jme3tools/nvtex/NVCompress.form @@ -0,0 +1,415 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/engine/src/tools/jme3tools/nvtex/NVCompress.java b/engine/src/tools/jme3tools/nvtex/NVCompress.java new file mode 100644 index 000000000..1968f4379 --- /dev/null +++ b/engine/src/tools/jme3tools/nvtex/NVCompress.java @@ -0,0 +1,920 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.nvtex; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.system.JmeSystem; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.plugins.HDRLoader; +import jme3tools.converters.ImageToAwt; +import jme3tools.converters.MipMapGenerator; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; +import javax.imageio.ImageIO; +import javax.swing.DefaultListModel; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.UIManager; + +public class NVCompress extends javax.swing.JFrame { + + private static Preferences pref = Preferences.userNodeForPackage(NVCompress.class); + private static File texToolsPath; + private static final String appName = "NVCompress GUI 1.00"; + private Thread workThread = null; + private AssetManager manager; + + public NVCompress() { + initComponents(); + barProgress.setVisible(false); + // puts the form in the center + setLocationRelativeTo(null); + System.out.println(appName); + setTitle(appName); + } + + /** 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + pnlMapType = new javax.swing.JPanel(); + cmbMapType = new javax.swing.JComboBox(); + chkMips = new javax.swing.JCheckBox(); + lblMapType = new javax.swing.JLabel(); + chkRepeat = new javax.swing.JCheckBox(); + pnlCompressOpt = new javax.swing.JPanel(); + cmbCompressType = new javax.swing.JComboBox(); + chkLowQuality = new javax.swing.JCheckBox(); + lblCompressType = new javax.swing.JLabel(); + chkCuda = new javax.swing.JCheckBox(); + sclFileList = new javax.swing.JScrollPane(); + lstFileList = new javax.swing.JList(); + btnAddFiles = new javax.swing.JButton(); + btnRemoveFiles = new javax.swing.JButton(); + pnlExportOpt = new javax.swing.JPanel(); + lblTargetDir = new javax.swing.JLabel(); + txtTargetDir = new javax.swing.JTextField(); + btnBrowse = new javax.swing.JButton(); + chkAsSource = new javax.swing.JCheckBox(); + btnCompress = new javax.swing.JButton(); + btnDecompress = new javax.swing.JButton(); + barProgress = new javax.swing.JProgressBar(); + jMenuBar1 = new javax.swing.JMenuBar(); + menuFile = new javax.swing.JMenu(); + itemExit = new javax.swing.JMenuItem(); + menuHelp = new javax.swing.JMenu(); + menuAbout = new javax.swing.JMenuItem(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle("NVCompress GUI"); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowClosing(java.awt.event.WindowEvent evt) { + formWindowClosing(evt); + } + }); + + pnlMapType.setBorder(javax.swing.BorderFactory.createTitledBorder("Input Options")); + + cmbMapType.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Color Map", "Normal Map", "Height/Convert to Normal Map" })); + + chkMips.setSelected(true); + chkMips.setText("Generate Mipmaps"); + + lblMapType.setText("Map Type: "); + + chkRepeat.setText("Repeating"); + + javax.swing.GroupLayout pnlMapTypeLayout = new javax.swing.GroupLayout(pnlMapType); + pnlMapType.setLayout(pnlMapTypeLayout); + pnlMapTypeLayout.setHorizontalGroup( + pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlMapTypeLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(chkRepeat) + .addComponent(chkMips) + .addGroup(pnlMapTypeLayout.createSequentialGroup() + .addComponent(lblMapType) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbMapType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(118, Short.MAX_VALUE)) + ); + pnlMapTypeLayout.setVerticalGroup( + pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlMapTypeLayout.createSequentialGroup() + .addGroup(pnlMapTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblMapType) + .addComponent(cmbMapType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(chkMips) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkRepeat) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pnlCompressOpt.setBorder(javax.swing.BorderFactory.createTitledBorder("Compression Options")); + + cmbCompressType.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "RGBA", "DXT1", "DXT1nm", "DXT1a", "DXT3", "DXT5", "DXT5nm", "ATI1", "ATI2/3Dc", "P4RGB565", "P8RGB565", "AWT", "PNG-RGBE" })); + cmbCompressType.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbCompressTypeActionPerformed(evt); + } + }); + + chkLowQuality.setText("Low Quality"); + + lblCompressType.setText("Compression Type: "); + + chkCuda.setSelected(true); + chkCuda.setText("Use GPU Compression (faster)"); + + javax.swing.GroupLayout pnlCompressOptLayout = new javax.swing.GroupLayout(pnlCompressOpt); + pnlCompressOpt.setLayout(pnlCompressOptLayout); + pnlCompressOptLayout.setHorizontalGroup( + pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlCompressOptLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlCompressOptLayout.createSequentialGroup() + .addComponent(chkLowQuality) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkCuda)) + .addGroup(pnlCompressOptLayout.createSequentialGroup() + .addComponent(lblCompressType) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbCompressType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(117, Short.MAX_VALUE)) + ); + pnlCompressOptLayout.setVerticalGroup( + pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlCompressOptLayout.createSequentialGroup() + .addGroup(pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkLowQuality) + .addComponent(chkCuda)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(pnlCompressOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblCompressType) + .addComponent(cmbCompressType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + lstFileList.setModel(new DefaultListModel()); + lstFileList.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyTyped(java.awt.event.KeyEvent evt) { + lstFileListKeyTyped(evt); + } + }); + sclFileList.setViewportView(lstFileList); + + btnAddFiles.setText("Add files.."); + btnAddFiles.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnAddFilesActionPerformed(evt); + } + }); + + btnRemoveFiles.setText("Remove Selected"); + btnRemoveFiles.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnRemoveFilesActionPerformed(evt); + } + }); + + pnlExportOpt.setBorder(javax.swing.BorderFactory.createTitledBorder("Export Options")); + + lblTargetDir.setText("Target Folder: "); + + btnBrowse.setText("Browse.."); + btnBrowse.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnBrowseActionPerformed(evt); + } + }); + + chkAsSource.setText("Same as source"); + chkAsSource.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkAsSourceActionPerformed(evt); + } + }); + + btnCompress.setText("Compress"); + btnCompress.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnCompressActionPerformed(evt); + } + }); + + btnDecompress.setText("Decompress"); + btnDecompress.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnDecompressActionPerformed(evt); + } + }); + + javax.swing.GroupLayout pnlExportOptLayout = new javax.swing.GroupLayout(pnlExportOpt); + pnlExportOpt.setLayout(pnlExportOptLayout); + pnlExportOptLayout.setHorizontalGroup( + pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlExportOptLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlExportOptLayout.createSequentialGroup() + .addComponent(lblTargetDir) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTargetDir, javax.swing.GroupLayout.DEFAULT_SIZE, 217, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(btnBrowse)) + .addComponent(chkAsSource) + .addGroup(pnlExportOptLayout.createSequentialGroup() + .addComponent(btnCompress) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnDecompress))) + .addContainerGap()) + ); + pnlExportOptLayout.setVerticalGroup( + pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnlExportOptLayout.createSequentialGroup() + .addGroup(pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblTargetDir) + .addComponent(txtTargetDir, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnBrowse)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkAsSource) + .addGap(7, 7, 7) + .addGroup(pnlExportOptLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnCompress) + .addComponent(btnDecompress)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + barProgress.setStringPainted(true); + + menuFile.setText("File"); + + itemExit.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Q, java.awt.event.InputEvent.CTRL_MASK)); + itemExit.setText("Exit"); + itemExit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + itemExitActionPerformed(evt); + } + }); + menuFile.add(itemExit); + + jMenuBar1.add(menuFile); + + menuHelp.setText("Help"); + + menuAbout.setText("About"); + menuAbout.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + menuAboutActionPerformed(evt); + } + }); + menuHelp.add(menuAbout); + + jMenuBar1.add(menuHelp); + + setJMenuBar(jMenuBar1); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(barProgress, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnlCompressOpt, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnlExportOpt, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnlMapType, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(btnAddFiles) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnRemoveFiles)) + .addComponent(sclFileList, javax.swing.GroupLayout.DEFAULT_SIZE, 263, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(pnlMapType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pnlCompressOpt, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlExportOpt, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(sclFileList, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(barProgress, javax.swing.GroupLayout.DEFAULT_SIZE, 30, Short.MAX_VALUE) + .addComponent(btnRemoveFiles, javax.swing.GroupLayout.PREFERRED_SIZE, 24, Short.MAX_VALUE) + .addComponent(btnAddFiles, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private String[] computeCompressParameters(){ + List params = new ArrayList(); + + if (!chkCuda.isSelected()) + params.add("-nocuda"); + + switch (cmbMapType.getSelectedIndex()){ + case 0: params.add("-color"); break; + case 1: params.add("-normal"); break; + case 2: params.add("-tonormal"); break; + } + + if (!chkMips.isSelected()) + params.add("-nomips"); + if (chkRepeat.isSelected()) + params.add("-repeat"); + if (chkLowQuality.isSelected()) + params.add("-fast"); + + switch (cmbCompressType.getSelectedIndex()){ + case 0: params.add("-rgb"); break; + case 1: params.add("-bc1"); break; + case 2: params.add("-bc1n"); break; + case 3: params.add("-bc1a"); break; + case 4: params.add("-bc2"); break; + case 5: params.add("-bc3"); break; + case 6: params.add("-bc3n"); break; + case 7: params.add("-bc4"); break; + case 8: params.add("-bc5"); break; + } + + return params.toArray(new String[0]); + } + + private void updateWork(String workStatus, int percent){ + barProgress.setString(workStatus + " - " + percent+"%"); + barProgress.setValue(percent); + } + + private void setComponentEnabled(Container c, boolean enabled){ + c.setEnabled(enabled); + for (Component child : c.getComponents()){ + if (child instanceof Container) + setComponentEnabled((Container)child, enabled); + else + child.setEnabled(enabled); + } + } + + private void startWork(){ + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + barProgress.setVisible(true); + barProgress.setValue(0); + barProgress.setString("Status: Working"); + + setComponentEnabled(pnlCompressOpt, false); + setComponentEnabled(pnlExportOpt, false); + setComponentEnabled(pnlMapType, false); + lstFileList.setEnabled(false); + btnAddFiles.setEnabled(false); + btnRemoveFiles.setEnabled(false); + } + + private void endWork(){ + workThread = null; + + setCursor(null); + barProgress.setVisible(false); + barProgress.setString("Status: Done"); + + setComponentEnabled(pnlCompressOpt, true); + setComponentEnabled(pnlExportOpt, true); + setComponentEnabled(pnlMapType, true); + lstFileList.setEnabled(true); + btnAddFiles.setEnabled(true); + btnRemoveFiles.setEnabled(true); + + // properly disables/enables certain components + chkAsSourceActionPerformed(null); + + btnCompress.setText("Compress"); + btnDecompress.setText("Decompress"); + } + + private void runJ3(File input, File output, String statusStr) throws InterruptedException{ + updateWork(statusStr, 0); + if (manager == null) + manager = JmeSystem.newAssetManager(); + + manager.registerLocator(input.getParent().toString(), + FileLocator.class.getName()); + + String format = (String) cmbCompressType.getSelectedItem(); + if (format.equals("PNG-RGBE")){ + HDRLoader loader = new HDRLoader(true); + try{ + FileInputStream in = new FileInputStream(input); + Image image = loader.load(in, false); + in.close(); + + BufferedImage rgbeImage = ImageToAwt.convert(image, false, true, 0); + if (output == null){ + output = new File(input.getParent(), input.getName() + ".png"); + } + ImageIO.write(rgbeImage, "png", output); + } catch (IOException ex){ + ex.printStackTrace(); + } + }else{ + Texture tex = manager.loadTexture(input.getName()); + Image image = tex.getImage(); + + boolean mips = chkMips.isSelected(); + if (mips && !image.hasMipmaps()){ + MipMapGenerator.generateMipMaps(image); + } + if (output == null){ + output = new File(input.getParent(), input.getName() + ".j3i"); + } + + try{ + BinaryExporter.getInstance().save(image, output); + BufferedImage preview = ImageToAwt.convert(image, false, true, 0); + ImageIO.write(preview, "png", new File(output + ".png")); + }catch (IOException ex){ + ex.printStackTrace(); + } + } + } + + private void runCommand(String[] args, String statusStr) throws InterruptedException{ + Process p = null; + try{ + ProcessBuilder builder = new ProcessBuilder(args); + updateWork(statusStr, 0); + p = builder.start(); + BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream())); + String ln; + while ((ln = r.readLine()) != null){ + if (Thread.interrupted()) + throw new InterruptedException(); + + if (ln.endsWith("%")){ + // show status in bar + int percent = Integer.parseInt(ln.substring(0, ln.length()-1)); + updateWork(statusStr, percent); + }else if (ln.startsWith("time taken")){ + ln = ln.substring(12, ln.length()-7).trim(); + System.out.println("Time Taken: "+ln+" seconds"); + } + } + r.close(); + int error = p.waitFor(); + if (error != 0){ + System.out.println("Error Code: " + error); + } + } catch (IOException ex){ + ex.printStackTrace(); + } catch (InterruptedException ex){ + // may get interrupted if user canceled work + if (p != null) + p.destroy(); + + // propegate exception incase caller is interested + throw ex; + } + } + + private void runNVDecompress(final File inFile) throws InterruptedException{ + if (!inFile.getName().toLowerCase().endsWith(".dds")) + return; // not a DDS file + + String[] args = new String[2]; + args[0] = new File(texToolsPath, "nvdecompress").toString(); + args[1] = inFile.toString(); + + System.out.println("Decompressing file "+inFile); + runCommand(args, "Decompressing file "+inFile.getName()); + } + + private void runNVCompress(final File inFile, File outFile) throws InterruptedException{ + String nvcompressCmd = new File(texToolsPath, "nvcompress").toString(); + int argCount = 2; // nvcompressCmd & inFile are always specified + if (outFile != null) + argCount ++; + + String[] params = computeCompressParameters(); + argCount += params.length; + + String[] args = new String[argCount]; + args[0] = nvcompressCmd; + System.arraycopy(params, 0, args, 1, params.length); + args[params.length + 1] = inFile.toString(); + if (outFile != null) + args[params.length + 2] = outFile.toString(); + + System.out.println("Converting file "+inFile); +// System.out.println("Arguments: "); +// for (String arg : args) System.out.println(" "+arg); + + runCommand(args, "Converting "+inFile.getName()); + } + + private void runJ3Compress(final File inFile, File outFile) throws InterruptedException{ + System.out.println("Converting file "+inFile); + runJ3(inFile, outFile, "Converting "+inFile.getName()); + } + + private Object[] compileFileList(){ + Object[] values = lstFileList.getSelectedValues(); + if (values == null || values.length == 0){ + // no specific files selected, add all of them + DefaultListModel listModel = (DefaultListModel) lstFileList.getModel(); + values = listModel.toArray(); + } + return values; + } + + private void runNVCompressAll(final File exportDir){ + final Object[] fileList = compileFileList(); + if (fileList != null && fileList.length > 0){ + startWork(); + workThread = new Thread(){ + @Override + public void run(){ + for (Object val : fileList){ + File inFile = (File) val; + File outFile = null; + if (exportDir != null){ + String name = inFile.getName(); + int extPt = name.lastIndexOf("."); + if (extPt > 0) + name = name.substring(0, extPt); + + outFile = new File(exportDir, name+".dds"); + } + try{ + runNVCompress(inFile, outFile); + }catch (InterruptedException ex){ + return; // user canceled + } + } + endWork(); + } + }; + workThread.setDaemon(true); + workThread.start(); + } + } + + private void runJ3CompressAll(final File exportDir, final String ext){ + final Object[] fileList = compileFileList(); + if (fileList != null && fileList.length > 0){ + startWork(); + workThread = new Thread(){ + @Override + public void run(){ + for (Object val : fileList){ + File inFile = (File) val; + File outFile = null; + if (exportDir != null){ + String name = inFile.getName(); + int extPt = name.lastIndexOf("."); + if (extPt > 0) + name = name.substring(0, extPt); + + outFile = new File(exportDir, name+"."+ext); + } + try{ + runJ3Compress(inFile, outFile); + }catch (InterruptedException ex){ + return; // user canceled + } + } + endWork(); + } + }; + workThread.setDaemon(true); + workThread.start(); + } + } + + private void runNVDecompressAll(){ + final Object[] fileList = compileFileList(); + if (fileList != null && fileList.length > 0){ + startWork(); + workThread = new Thread(){ + @Override + public void run(){ + for (Object val : fileList){ + File inFile = (File) val; + try{ + runNVDecompress(inFile); + }catch (InterruptedException ex){ + return; // user canceled + } + } + endWork(); + } + }; + workThread.setDaemon(true); + workThread.start(); + } + } + + private void cmbCompressTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbCompressTypeActionPerformed + +}//GEN-LAST:event_cmbCompressTypeActionPerformed + + private void btnCompressActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCompressActionPerformed + if (btnCompress.getText().equals("Cancel")){ + if (workThread != null){ + workThread.interrupt(); + System.out.println("User canceled decompression"); + endWork(); + } + }else{ + // find out where to export + File exportDir = null; + if (!chkAsSource.isSelected()){ + String exportPath = txtTargetDir.getText(); + if (exportPath != null && !exportPath.equals("")) + exportDir = new File(exportPath); + } + + String compression = (String) cmbCompressType.getSelectedItem(); + if (compression.equals("AWT") || compression.equals("PNG-RGBE")){ + runJ3CompressAll(exportDir, compression.equals("AWT") ? "j3i" : "pnge"); + }else{ + runNVCompressAll(exportDir); + } + + btnCompress.setEnabled(true); + btnCompress.setText("Cancel"); + } +}//GEN-LAST:event_btnCompressActionPerformed + + private void btnBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseActionPerformed + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Select export directory"); + chooser.setMultiSelectionEnabled(false); + chooser.setDialogType(JFileChooser.OPEN_DIALOG); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (txtTargetDir.getText() != null && !txtTargetDir.getText().equals("")) + chooser.setSelectedFile(new File(txtTargetDir.getText())); + + if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION){ + File f = chooser.getSelectedFile(); + if (f != null && f.exists() && f.isDirectory()) + txtTargetDir.setText(f.toString()); + else + JOptionPane.showMessageDialog(this, + "Invalid export directory specified", + "Error", JOptionPane.ERROR_MESSAGE); + } + }//GEN-LAST:event_btnBrowseActionPerformed + + private void chkAsSourceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkAsSourceActionPerformed + lblTargetDir.setEnabled(!chkAsSource.isSelected()); + txtTargetDir.setEnabled(!chkAsSource.isSelected()); + btnBrowse.setEnabled(!chkAsSource.isSelected()); + }//GEN-LAST:event_chkAsSourceActionPerformed + + private void btnAddFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddFilesActionPerformed + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Add input files"); + chooser.setMultiSelectionEnabled(true); + chooser.setDialogType(JFileChooser.OPEN_DIALOG); + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){ + File[] files = chooser.getSelectedFiles(); + for (File file : files){ + if (file.exists() && !file.isDirectory()){ + // add to file list + DefaultListModel listModel = (DefaultListModel) lstFileList.getModel(); + if (!listModel.contains(file)) + listModel.addElement(file); + } + } + } + }//GEN-LAST:event_btnAddFilesActionPerformed + + private void btnRemoveFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRemoveFilesActionPerformed + Object[] selected = lstFileList.getSelectedValues(); + DefaultListModel listModel = (DefaultListModel) lstFileList.getModel(); + for (Object val : selected){ + listModel.removeElement(val); + } + }//GEN-LAST:event_btnRemoveFilesActionPerformed + + private void itemExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_itemExitActionPerformed + dispose(); +}//GEN-LAST:event_itemExitActionPerformed + + private void menuAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuAboutActionPerformed + String aboutText = appName+"\n"+ + "Created by Kirill Vainer.\n"+ + "\n"+ + "NVIDIA Texture Tools is Copyright© 2009 NVIDIA Corporation.\n"+ + "\n"+ + "Usage: \n"+ + " Press \"Add Files..\" to add files to convert\n"+ + " Select conversion options on the left, then\n"+ + " click \"Export\" to convert files to DDS\n"; + JOptionPane.showMessageDialog(this, aboutText, "About", JOptionPane.PLAIN_MESSAGE); + }//GEN-LAST:event_menuAboutActionPerformed + + private void lstFileListKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_lstFileListKeyTyped + if (evt.getKeyCode() == KeyEvent.VK_DELETE){ + btnRemoveFilesActionPerformed(null); + } + }//GEN-LAST:event_lstFileListKeyTyped + + private void btnDecompressActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDecompressActionPerformed + if (btnDecompress.getText().equals("Cancel")){ + if (workThread != null){ + workThread.interrupt(); + System.out.println("User canceled decompression"); + endWork(); + } + }else{ + runNVDecompressAll(); + btnDecompress.setEnabled(true); + btnDecompress.setText("Cancel"); + } + +}//GEN-LAST:event_btnDecompressActionPerformed + + private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing + if (workThread != null) + workThread.interrupt(); + }//GEN-LAST:event_formWindowClosing + + private static boolean verifyTexToolsPath(File path){ + if (path.exists()){ + File[] files = path.listFiles(); + for (File f : files){ + if (f.getName().startsWith("nvcompress")){ + return true; + } + } + } + return false; + } + + private static File showToolsPathChooser(File prevPath){ + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Select directory of NVTextureTools"); + chooser.setMultiSelectionEnabled(false); + chooser.setDialogType(JFileChooser.OPEN_DIALOG); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (prevPath != null) + chooser.setSelectedFile(prevPath); + + if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION){ + return chooser.getSelectedFile(); + }else{ + return null; + } + } + + public static String attemptLocateToolsPath(){ + String path = pref.get("NVTextureToolsPath", null); + if (path == null){ + // if texture tools has been properly installed + // the path is located under TEXTURE_TOOLS_DIR env var + path = System.getenv("TEXTURE_TOOLS_DIR"); + } + return path; + } + + public static void main(String args[]) { + try{ + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ex){ + ex.printStackTrace(); + } + + System.out.println("Verifiying texture tools path.."); + String path = attemptLocateToolsPath(); + texToolsPath = path == null ? null : new File(path); + + if (texToolsPath != null){ + System.out.println("Found existing path: "+texToolsPath); + } + + if (texToolsPath == null || !verifyTexToolsPath(texToolsPath)){ + while (true){ + File f = showToolsPathChooser(texToolsPath); + if (f == null) + return; + + if (verifyTexToolsPath(f)){ + texToolsPath = f; + pref.put("NVTextureToolsPath", texToolsPath.toString()); + System.out.println("User specified valid path: "+texToolsPath); + try{ + pref.flush(); + } catch (BackingStoreException ex){} + break; + }else{ + JOptionPane.showMessageDialog(null, "Directory must"+ + " contain nvcompress", + "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new NVCompress().setVisible(true); + } + }); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JProgressBar barProgress; + private javax.swing.JButton btnAddFiles; + private javax.swing.JButton btnBrowse; + private javax.swing.JButton btnCompress; + private javax.swing.JButton btnDecompress; + private javax.swing.JButton btnRemoveFiles; + private javax.swing.JCheckBox chkAsSource; + private javax.swing.JCheckBox chkCuda; + private javax.swing.JCheckBox chkLowQuality; + private javax.swing.JCheckBox chkMips; + private javax.swing.JCheckBox chkRepeat; + private javax.swing.JComboBox cmbCompressType; + private javax.swing.JComboBox cmbMapType; + private javax.swing.JMenuItem itemExit; + private javax.swing.JMenuBar jMenuBar1; + private javax.swing.JLabel lblCompressType; + private javax.swing.JLabel lblMapType; + private javax.swing.JLabel lblTargetDir; + private javax.swing.JList lstFileList; + private javax.swing.JMenuItem menuAbout; + private javax.swing.JMenu menuFile; + private javax.swing.JMenu menuHelp; + private javax.swing.JPanel pnlCompressOpt; + private javax.swing.JPanel pnlExportOpt; + private javax.swing.JPanel pnlMapType; + private javax.swing.JScrollPane sclFileList; + private javax.swing.JTextField txtTargetDir; + // End of variables declaration//GEN-END:variables + +} diff --git a/engine/src/tools/jme3tools/optimize/FastOctnode.java b/engine/src/tools/jme3tools/optimize/FastOctnode.java new file mode 100644 index 000000000..821559db8 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/FastOctnode.java @@ -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. + */ + +package jme3tools.optimize; + +import com.jme3.bounding.BoundingBox; +import com.jme3.math.Vector3f; +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 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 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 000000000..5b705750c --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java @@ -0,0 +1,313 @@ +package jme3tools.optimize; + +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +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.Node; +import com.jme3.scene.Spatial; +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.scene.mesh.VirtualIndexBuffer; +import com.jme3.scene.mesh.WrappedIndexBuffer; +import com.jme3.scene.shape.Quad; +import com.jme3.util.IntMap.Entry; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GeometryBatchFactory { + + 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); + } + } + + /** + * Merges all geometries in the collection into + * the output mesh. Does not take into account materials. + * + * @param geometries + * @param outMesh + */ + public static void mergeGeometries(Collection geometries, Mesh outMesh){ + int[] compsForBuf = new int[VertexBuffer.Type.values().length]; + Format[] formatForBuf = new Format[compsForBuf.length]; + + int totalVerts = 0; + int totalTris = 0; + + Mode mode = null; + for (Geometry geom : geometries){ + totalVerts += geom.getVertexCount(); + totalTris += geom.getTriangleCount(); + + 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 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 (outBuf == null) + continue; + + if (Type.Index.ordinal() == bufType){ + int components = compsForBuf[bufType]; + + IndexBuffer inIdx = inMesh.getIndexBuffer(); + if (inIdx == null){ + inIdx = new VirtualIndexBuffer(geomVertCount, inMesh.getMode()); + }else if (inMesh.getMode() != mode){ + inIdx = new WrappedIndexBuffer(inMesh); + } + 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.getData(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix); + }else if (Type.Normal.ordinal() == bufType || Type.Tangent.ordinal() == bufType){ + FloatBuffer inPos = (FloatBuffer) inBuf.getData(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix); + }else{ + for (int vert = 0; vert < geomVertCount; vert++){ + int curGlobalVertIndex = globalVertIndex + vert; + inBuf.copyElement(vert, outBuf, curGlobalVertIndex); + } + } + } + + globalVertIndex += geomVertCount; + globalTriIndex += geomTriCount; + } + } + + + public static List makeBatches(Collection geometries){ + ArrayList retVal = new ArrayList(); + HashMap> matToGeom = new HashMap>(); + + for (Geometry geom : geometries){ + List outList = matToGeom.get(geom.getMaterial()); + if (outList == null){ + outList = new ArrayList(); + matToGeom.put(geom.getMaterial(), outList); + } + outList.add(geom); + } + + int batchNum = 0; + for (Map.Entry> entry : matToGeom.entrySet()){ + Material mat = entry.getKey(); + List geomsForMat = entry.getValue(); + Mesh mesh = new Mesh(); + mergeGeometries(geomsForMat, mesh); + mesh.updateCounts(); + mesh.updateBound(); + + Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh); + out.setMaterial(mat); + retVal.add(out); + } + + return retVal; + } + + private static void gatherGeoms(Spatial scene, List 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); + } + } + + public static Spatial optimize(Spatial scene){ + ArrayList geoms = new ArrayList(); + gatherGeoms(scene, geoms); + + List batchedGeoms = makeBatches(geoms); + + Node node = new Node(scene.getName()); + for (Geometry geom : batchedGeoms){ + node.attachChild(geom); + } + + return node; + } + + 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 geoms = new ArrayList(); + 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 000000000..b31a8a3b1 --- /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 000000000..12c4d9337 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/Octnode.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +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 tris; + Geometry[] geoms; + final Octnode[] children = new Octnode[8]; + boolean leaf = false; + FastOctnode fastNode; + + public Octnode(BoundingBox bbox, ArrayList 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 keepTris = new ArrayList(); + 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(); + } + + 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 globalGeomList){ + fastNode = new FastOctnode(); + + if (geoms != null){ + Collection geomsColl = Arrays.asList(geoms); + List 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 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 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 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 000000000..880f98459 --- /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 allTris = new ArrayList(); + private final Geometry[] geoms; + private final BoundingBox bbox; + private final int minTrisPerNode; + private Octnode root; + + private CollisionResults boundResults = new CollisionResults(); + + private static List getGeometries(Spatial scene){ + if (scene instanceof Geometry){ + List geomList = new ArrayList(1); + geomList.add((Geometry) scene); + return geomList; + }else if (scene instanceof Node){ + Node n = (Node) scene; + List geoms = new ArrayList(); + 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 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 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 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 000000000..cc451aa3e --- /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 tris = new ArrayList(); + tris.add(new OCTTriangle(z, z, z, 1, 0)); + tris.add(new OCTTriangle(z, z, z, 0, 1)); + List firstOne = TriangleCollector.gatherTris(new Geometry[]{ g, g2 }, tris); + System.out.println(firstOne.get(0).getMesh()); + } + +} diff --git a/engine/src/tools/jme3tools/optimize/TestOctree.java b/engine/src/tools/jme3tools/optimize/TestOctree.java new file mode 100644 index 000000000..e166b73f1 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/TestOctree.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.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; + + +public class TestOctree extends SimpleApplication implements SceneProcessor { + + private Octree tree; + private FastOctnode fastRoot; + private Geometry[] globalGeoms; + private BoundingBox octBox; + + private Set renderSet = new HashSet(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 globalGeomList = new ArrayList(); + 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/tools/jme3tools/optimize/TriangleCollector.java b/engine/src/tools/jme3tools/optimize/TriangleCollector.java new file mode 100644 index 000000000..9001c53e7 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/TriangleCollector.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TriangleCollector { + + private static final GeomTriComparator comparator = new GeomTriComparator(); + + private static class GeomTriComparator implements Comparator { + 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 tris from the input array + * (using the indices OCTTriangle.getGeometryIndex() & OCTTriangle.getTriangleIndex()) + * then organizes them into output geometry. + * + * @param geoms + * @param tris + * @return + */ + public static final List gatherTris(Geometry[] inGeoms, List tris){ + Collections.sort(tris, comparator); + HashMap ranges = new HashMap(); + + 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 newGeoms = new ArrayList(); + int[] vertIndicies = new int[3]; + int[] newIndices = new int[3]; + boolean[] vertexCreated = new boolean[3]; + HashMap indexCache = new HashMap(); + for (Map.Entry 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 bufs = in.getBuffers(); + for (Entry 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 inbufs = in.getBuffers(); + for (Entry 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 outbufs = out.getBuffers(); + for (Entry 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 000000000..61d83a566 --- /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/xml/com/jme3/export/xml/DOMInputCapsule.java b/engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java new file mode 100644 index 000000000..9860555e5 --- /dev/null +++ b/engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java @@ -0,0 +1,1490 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.binary.BinaryClassLoader; +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.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * 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 referencedSavables = new HashMap(); + + public DOMInputCapsule(Document doc, XMLImporter importer) { + this.doc = doc; + this.importer = importer; + currentElem = doc.getDocumentElement(); + } + + private static String decodeString(String s) { + if (s == null) { + return null; + } + s = s.replaceAll("\\"", "\"").replaceAll("\\<", "<").replaceAll("\\&", "&"); + 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 byteArrays = new ArrayList(); + + 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 intArrays = new ArrayList(); + + 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 doubleArrays = new ArrayList(); + + 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 longArrays = new ArrayList(); + + 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 shortArrays = new ArrayList(); + + 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 booleanArrays = new ArrayList(); + + 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 strings = new ArrayList(); + + 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 stringArrays = new ArrayList(); + + 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 = BinaryClassLoader.fromName(className, 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) { + 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 savables = new ArrayList(); + 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 readSavableArrayList(String name, ArrayList defVal) throws IOException { + try { + Element tmpEl = findChildElement(currentElem, name); + if (tmpEl == null) { + return defVal; + } + + String sizeString = tmpEl.getAttribute("size"); + ArrayList savables = new ArrayList(); + 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[] 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 sal; + List> savableArrayLists = + new ArrayList>(); + 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[][] 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[] arr; + List[]> sall = new ArrayList[]>(); + 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 readFloatBufferArrayList( + String name, ArrayList defVal) throws IOException { + try { + Element tmpEl = findChildElement(currentElem, name); + if (tmpEl == null) { + return defVal; + } + + String sizeString = tmpEl.getAttribute("size"); + ArrayList tmp = new ArrayList(); + 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 readSavableMap(String name, Map defVal) throws IOException { + Map ret; + Element tempEl; + + if (name != null) { + tempEl = findChildElement(currentElem, name); + } else { + tempEl = currentElem; + } + ret = new HashMap(); + + 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 readStringSavableMap(String name, Map defVal) throws IOException { + Map ret = null; + Element tempEl; + + if (name != null) { + tempEl = findChildElement(currentElem, name); + } else { + tempEl = currentElem; + } + if (tempEl != null) { + ret = new HashMap(); + + 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 readIntSavableMap(String name, IntMap defVal) throws IOException { + IntMap ret = null; + Element tempEl; + + if (name != null) { + tempEl = findChildElement(currentElem, name); + } else { + tempEl = currentElem; + } + if (tempEl != null) { + ret = new IntMap(); + + 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 readByteBufferArrayList(String name, ArrayList defVal) throws IOException { + try { + Element tmpEl = findChildElement(currentElem, name); + if (tmpEl == null) { + return defVal; + } + + String sizeString = tmpEl.getAttribute("size"); + ArrayList tmp = new ArrayList(); + 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 readEnum(String name, Class 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 000000000..037576f54 --- /dev/null +++ b/engine/src/xml/com/jme3/export/xml/DOMOutputCapsule.java @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 com.jme3.export.Savable; +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.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +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 writtenSavables = new IdentityHashMap(); + + 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 = null; + ret = doc.createElement(name); + if (currentElement == null) { + 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("\\&", "&") + .replaceAll("\\\"", """) + .replaceAll("\\<", "<"); + 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= 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); + 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 array, String name, ArrayList 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 map, String name, Map defVal) throws IOException { + if (map == null) { + return; + } + if (map.equals(defVal)) { + return; + } + Element stringMap = appendElement(name); + + Iterator 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 map, String name, Map defVal) throws IOException { + if (map == null) { + return; + } + if (map.equals(defVal)) { + return; + } + Element stringMap = appendElement(name); + + Iterator 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 map, String name, IntMap defVal) throws IOException { + if (map == null) { + return; + } + if (map.equals(defVal)) { + return; + } + Element stringMap = appendElement(name); + + for(Entry 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 array, + String name, ArrayList 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 000000000..4242b4a17 --- /dev/null +++ b/engine/src/xml/com/jme3/export/xml/DOMSerializer.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.export.xml; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * 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("<"); + break; + case '>': + writer.write(">"); + break; + case '&': + writer.write("&"); + break; + case '\r': + writer.write(" "); + 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. + *

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

+ * 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(""); + break; + case Node.COMMENT_NODE: + for (int i = 0; i < depth; ++i) { writer.append(' '); } + writer.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(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("").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("').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(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 000000000..dccb15d71 --- /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 000000000..d4aac7ee1 --- /dev/null +++ b/engine/src/xml/com/jme3/export/xml/XMLImporter.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.export.xml; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetManager; +import com.jme3.export.JmeImporter; +import com.jme3.export.InputCapsule; +import com.jme3.export.Savable; +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; + // A single-load-state base URI for texture-loading. + + public XMLImporter() { + } + + 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; + } + + synchronized public Savable load(InputStream f) throws IOException { + /* Leave this method synchronized. Calling this method from more than + * one thread at a time for the same XMLImporter instance will clobber + * the XML Document instantiated here. */ + 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(); + } + +} diff --git a/engine/test/com/jme/animation/CompactQuaternionArrayTest.java b/engine/test/com/jme/animation/CompactQuaternionArrayTest.java new file mode 100644 index 000000000..81ff20461 --- /dev/null +++ b/engine/test/com/jme/animation/CompactQuaternionArrayTest.java @@ -0,0 +1,48 @@ +package com.jme.animation; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +import com.jme3.animation.CompactQuaternionArray; +import com.jme3.math.Quaternion; + +public class CompactQuaternionArrayTest { + + @Before + public void setUp() throws Exception { + } + + @Test + public void testCompactQuaternionArrayQuaternionArray() { + Quaternion[] objArray = new Quaternion[] { + new Quaternion(1, 0, 1, 1), + new Quaternion(1, 1, 1, 0), + new Quaternion(0, 1, 1, 0), + new Quaternion(1, 1, 1, 0), + new Quaternion(1, 0, 1, 1), + }; + CompactQuaternionArray compact = new CompactQuaternionArray(); + compact.add(objArray); + assertTrue(Arrays.equals(compact.getIndex(objArray), new int[] {0, 1, 2, 1, 0})); + assertTrue(Arrays.equals(compact.getSerializedData(), new float[] {1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0})); + } + + @Test + public void testCompactQuaternionArrayDoubleArrayIntArray() { + int[] indexArray = new int[] {0, 1, 2, 1, 0}; + float[] dataArray = new float[] {1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0}; + Quaternion[] objArray = new Quaternion[] { + new Quaternion(1, 0, 1, 1), + new Quaternion(1, 1, 1, 0), + new Quaternion(0, 1, 1, 0), + new Quaternion(1, 1, 1, 0), + new Quaternion(1, 0, 1, 1), + }; + CompactQuaternionArray compact = new CompactQuaternionArray(dataArray, indexArray); + assertTrue(Arrays.deepEquals(compact.toObjectArray(), objArray)); + } +} diff --git a/engine/test/com/jme/animation/CompactVector3ArrayTest.java b/engine/test/com/jme/animation/CompactVector3ArrayTest.java new file mode 100644 index 000000000..70b38fbcd --- /dev/null +++ b/engine/test/com/jme/animation/CompactVector3ArrayTest.java @@ -0,0 +1,161 @@ +package com.jme.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +import com.jme3.animation.CompactVector3Array; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.math.Vector3f; + +public class CompactVector3ArrayTest { + private final Vector3f[] objArray1 = new Vector3f[] { + new Vector3f(1, 0, 1), // 0 + new Vector3f(1, 1, 1), // 1 + new Vector3f(0, 1, 1), // 2 + new Vector3f(1, 1, 1), // 1 + new Vector3f(1, 0, 1), // 0 + }; + private final Vector3f[] objArray2 = new Vector3f[] { + new Vector3f(1, 0, 2), // 3 + new Vector3f(1, 1, 1), // 1 + new Vector3f(0, 1, 1), // 2 + null, // -1 + new Vector3f(1, 0, 2), // 3 + }; + private static final int[] index1 = new int[] {0, 1, 2, 1, 0}; + private static final int[] index2 = new int[] {3, 1, 2, -1, 3}; + private int[] index12; + private static final float[] serialData = new float[] {1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 2}; + + CompactVector3Array compact; + + @Before + public void setUp() throws Exception { + compact = new CompactVector3Array(); + index12 = Arrays.copyOf(index1, index1.length+index2.length); + System.arraycopy(index2, 0, index12, index1.length, index2.length); + } + + @Test + public void testCompactVector3ArrayAdd() { + compact.add(objArray1); + compact.add(objArray2); + _testAdd(); + + try { + compact.freeze(); + compact.add(objArray1); + fail(); + } catch (Exception e) { + } + } + + private void _testAdd() { + assertTrue(Arrays.equals(compact.getIndex(objArray1), index1)); + assertTrue(Arrays.equals(compact.getIndex(objArray2), index2)); + assertTrue(Arrays.equals(compact.getSerializedData(), serialData)); + } + + @Test + public void testCompactVector3ArrayFloatArrayIntArray() { + int[] indexArray = index1; + float[] dataArray = new float[] {1, 0, 1, 1, 1, 1, 0, 1, 1}; + Vector3f[] objArray = new Vector3f[] { + new Vector3f(1, 0, 1), + new Vector3f(1, 1, 1), + new Vector3f(0, 1, 1), + new Vector3f(1, 1, 1), + new Vector3f(1, 0, 1), + }; + CompactVector3Array compact = new CompactVector3Array(dataArray, indexArray); + assertTrue(Arrays.deepEquals(compact.toObjectArray(), objArray)); + } + + @Test + public void testGetTotalObjectSize() { + compact.add(objArray1); + assertTrue(compact.getTotalObjectSize() == 5); + assertTrue(compact.getCompactObjectSize() == 3); + compact.add(objArray2); + _testSize(); + } + + private void _testSize() { + assertTrue(compact.getTotalObjectSize() == 10); + assertTrue(compact.getCompactObjectSize() == 4); + } + + @Test + public void testGet() { + compact.add(objArray1); + Vector3f v1 = compact.get(1, new Vector3f()); + assertEquals(new Vector3f(1, 1, 1), v1); + compact.add(objArray2); + _testGet(); + } + + private void _testGet() { + Vector3f v2 = compact.get(1, new Vector3f()); + assertEquals(new Vector3f(1, 1, 1), v2); + Vector3f v3 = compact.get(5, new Vector3f()); + assertEquals(new Vector3f(1, 0, 2), v3); + } + + @Test + public void testGetCompactIndex() { + compact.add(objArray1); + compact.add(objArray2); + _testCompactIndex(); + } + + private void _testCompactIndex() { + for (int i = 0; i < index12.length; i++) { + assertEquals(index12[i], compact.getCompactIndex(i)); + } + } + + @Test + public void testGetIndex() { + compact.add(objArray1); + compact.add(objArray2); + _testGetIndex(); + } + + private void _testGetIndex() { + Vector3f[] reverse = new Vector3f[objArray1.length]; + int[] reverseIndex = new int[objArray1.length]; + for (int i = 0; i < objArray1.length; i++) { + reverse[i] = objArray1[objArray1.length-1-i]; + reverseIndex[i] = index1[objArray1.length-1-i]; + } + + int[] index = compact.getIndex(reverse); + for (int i = 0; i < index.length; i++) { + assertEquals(reverseIndex[i], index[i]); + } + } + + @Test + public void testRead() throws IOException { + File file = File.createTempFile("compactArray", "test"); + BinaryImporter importer = new BinaryImporter(); + BinaryExporter exporter = new BinaryExporter(); + compact.add(objArray1); + compact.add(objArray2); + exporter.save(compact, file); + compact = (CompactVector3Array) importer.load(file); + _testSize(); + _testCompactIndex(); + _testGet(); + file.delete(); + } +} diff --git a/engine/test/com/jme3/font/ColorTagsTest.java b/engine/test/com/jme3/font/ColorTagsTest.java new file mode 100644 index 000000000..47b3c9626 --- /dev/null +++ b/engine/test/com/jme3/font/ColorTagsTest.java @@ -0,0 +1,46 @@ +package com.jme3.font; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ColorTagsTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testGetPureText() { + String str1 = "abcde test"; + ColorTags tag1 = new ColorTags(str1); + assertEquals(tag1.getPlainText(), str1); + + String str2 = "abcde\\#1A3d#test\\#1A3#"; + ColorTags tag2 = new ColorTags(str2); + assertEquals("abcdetest", tag2.getPlainText()); + } + + @Test + public void testGetTags() { + String str1 = "abcde test"; + ColorTags tag1 = new ColorTags(str1); + assertTrue(tag1.getTags().isEmpty()); + + String str2 = "abcde\\#1A3d#test\\#abef1211#gogo\\#abef12#yeye\\#ab1#hey"; + ColorTags tag2 = new ColorTags(str2); + assertEquals(4, tag2.getTags().size()); + assertEquals("abcdetestgogoyeyehey", tag2.getPlainText()); + assertEquals(5, tag2.getTags().get(0).start); + assertEquals(9, tag2.getTags().get(1).start); + assertEquals(13, tag2.getTags().get(2).start); + assertEquals(17, tag2.getTags().get(3).start); + } + +} diff --git a/engine/test/com/jme3/math/TrigonometryTest.java b/engine/test/com/jme3/math/TrigonometryTest.java new file mode 100644 index 000000000..df778f6f4 --- /dev/null +++ b/engine/test/com/jme3/math/TrigonometryTest.java @@ -0,0 +1,28 @@ +package com.jme3.math; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import org.junit.Test; + +public class TrigonometryTest { + + @Test + public void testVector2(){ + Vector2f original = new Vector2f(1, 2); + Vector2f recreated = new Vector2f(); + + float angle = original.getAngle(); + float length = original.length(); + + recreated.set( FastMath.cos(angle), FastMath.sin(angle) ); + recreated.multLocal(length); + + assertEquals( original.getX(), recreated.getX(), 0.000001 ); + assertEquals( original.getY(), recreated.getY(), 0.000001 ); + } + +} diff --git a/engine/town.zip b/engine/town.zip new file mode 100644 index 000000000..dc936321e Binary files /dev/null and b/engine/town.zip differ diff --git a/engine/wildhouse.zip b/engine/wildhouse.zip new file mode 100644 index 000000000..ea9cc26eb Binary files /dev/null and b/engine/wildhouse.zip differ